mirror of
https://github.com/selfxyz/self.git
synced 2026-01-10 23:27:56 -05:00
refactor utils, inputs and constants into /common
This commit is contained in:
16
app/App.tsx
16
app/App.tsx
@@ -38,7 +38,7 @@ import { config } from "@gluestack-ui/config" // Optional if you want to use def
|
||||
|
||||
// @ts-ignore
|
||||
import PassportReader from 'react-native-passport-reader';
|
||||
import {checkInputs, getFirstName} from './utils/checks';
|
||||
import {getFirstName, formatDuration, formatProof} from './utils/utils';
|
||||
import {
|
||||
DEFAULT_PNUMBER,
|
||||
DEFAULT_DOB,
|
||||
@@ -46,9 +46,17 @@ import {
|
||||
DEFAULT_ADDRESS,
|
||||
LOCAL_IP,
|
||||
} from '@env';
|
||||
import {DataHash, PassportData} from './types/passportData';
|
||||
import {arraysAreEqual, bytesToBigDecimal, dataHashesObjToArray, formatAndConcatenateDataHashes, formatDuration, formatMrz, formatProof, splitToWords} from './utils/utils';
|
||||
import {hash, toUnsignedByte} from './utils/computeEContent';
|
||||
import {DataHash, PassportData} from '../common/src/utils/types';
|
||||
import {
|
||||
hash,
|
||||
toUnsignedByte,
|
||||
checkInputs,
|
||||
bytesToBigDecimal,
|
||||
dataHashesObjToArray,
|
||||
formatAndConcatenateDataHashes,
|
||||
formatMrz,
|
||||
splitToWords
|
||||
} from '../common/src/utils/utils';
|
||||
|
||||
console.log('DEFAULT_PNUMBER', DEFAULT_PNUMBER);
|
||||
console.log('LOCAL_IP', LOCAL_IP);
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
|
||||
const path = require('path');
|
||||
const extraNodeModules = {
|
||||
'common': path.resolve(__dirname + '/../common'),
|
||||
};
|
||||
const watchFolders = [
|
||||
path.resolve(__dirname + '/../common')
|
||||
];
|
||||
|
||||
/**
|
||||
* Metro configuration
|
||||
@@ -6,6 +13,11 @@ const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
|
||||
*
|
||||
* @type {import('metro-config').MetroConfig}
|
||||
*/
|
||||
const config = {};
|
||||
const config = {
|
||||
resolver: {
|
||||
extraNodeModules,
|
||||
},
|
||||
watchFolders,
|
||||
};
|
||||
|
||||
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"@types/react-native-dotenv": "^0.2.0",
|
||||
"@types/react-test-renderer": "^18.0.0",
|
||||
"babel-jest": "^29.2.1",
|
||||
"babel-plugin-module-resolver": "^5.0.0",
|
||||
"eslint": "^8.19.0",
|
||||
"jest": "^29.2.1",
|
||||
"metro-react-native-babel-preset": "0.76.7",
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
import * as crypto from 'crypto';
|
||||
import {
|
||||
arraysAreEqual,
|
||||
dataHashesObjToArray,
|
||||
formatMrz,
|
||||
assembleMrz,
|
||||
findTimeOfSignature,
|
||||
parsePubKeyString,
|
||||
formatAndConcatenateDataHashes,
|
||||
assembleEContent,
|
||||
} from '../utils/utils';
|
||||
import * as forge from 'node-forge';
|
||||
import passportData from '../server/passportData.json';
|
||||
import {DataHash} from '../types/passportData';
|
||||
|
||||
// This script tests the whole flow from MRZ to signature
|
||||
// The passportData is imported from passportData.json written by the server
|
||||
|
||||
const mrz = passportData.mrz;
|
||||
|
||||
console.log('mrz: ', mrz);
|
||||
|
||||
// Transforms the dataHashes object into an array of arrays
|
||||
const dataHashes = passportData.dataGroupHashes as DataHash[];
|
||||
|
||||
const mrzHash = hash(formatMrz(mrz));
|
||||
|
||||
console.log('mrzHash:', mrzHash);
|
||||
console.log(
|
||||
'mrzHash === dataHashes[0][1] ?',
|
||||
arraysAreEqual(mrzHash, dataHashes[0][1] as number[]),
|
||||
);
|
||||
|
||||
const concatenatedDataHashes = formatAndConcatenateDataHashes(
|
||||
mrzHash,
|
||||
dataHashes,
|
||||
);
|
||||
|
||||
const concatenatedDataHashesHashDigest = hash(concatenatedDataHashes);
|
||||
|
||||
// check that concatenatedDataHashesHashDigest is at the right place of passportData.eContent
|
||||
const sliceOfEContent = passportData.eContent.slice(72, 72 + 32);
|
||||
|
||||
console.log(
|
||||
'Are they equal ?',
|
||||
arraysAreEqual(sliceOfEContent, concatenatedDataHashesHashDigest),
|
||||
);
|
||||
|
||||
// now let's verify the signature
|
||||
// const {modulus, exponent} = parsePubKeyString(passportData.publicKey);
|
||||
// Create the public key
|
||||
const rsa = forge.pki.rsa;
|
||||
const publicKey = rsa.setPublicKey(
|
||||
new forge.jsbn.BigInteger(passportData.modulus, 10),
|
||||
new forge.jsbn.BigInteger("10001", 16),
|
||||
);
|
||||
|
||||
// SHA-256 hash of the eContent
|
||||
const md = forge.md.sha256.create();
|
||||
md.update(forge.util.binary.raw.encode(new Uint8Array(passportData.eContent)));
|
||||
const hashOfEContent = md.digest().getBytes();
|
||||
|
||||
console.log('modulus', passportData.modulus);
|
||||
console.log('eContent', bytesToBigDecimal(passportData.eContent));
|
||||
console.log('signature', bytesToBigDecimal(passportData.encryptedDigest));
|
||||
|
||||
// Signature verification
|
||||
const signatureBytes = Buffer.from(passportData.encryptedDigest).toString(
|
||||
'binary',
|
||||
);
|
||||
const valid = publicKey.verify(hashOfEContent, signatureBytes);
|
||||
|
||||
if (valid) {
|
||||
console.log('The signature is valid.');
|
||||
} else {
|
||||
console.log('The signature is not valid.');
|
||||
}
|
||||
|
||||
function hash(bytesArray: number[]): number[] {
|
||||
const hash = crypto.createHash('sha256');
|
||||
hash.update(Buffer.from(bytesArray));
|
||||
return Array.from(hash.digest()).map(x => (x < 128 ? x : x - 256));
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
function hexToDecimal(hex: string): string {
|
||||
return BigInt(`0x${hex}`).toString();
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
export type MrzInfo = {
|
||||
compositeCheckDigit: string;
|
||||
dateOfBirth: string;
|
||||
dateOfBirthCheckDigit: string;
|
||||
dateOfExpiry: string;
|
||||
dateOfExpiryCheckDigit: string;
|
||||
documentCode: string;
|
||||
documentNumber: string;
|
||||
documentNumberCheckDigit: string;
|
||||
documentType: number;
|
||||
gender: string;
|
||||
issuingState: string;
|
||||
nationality: string;
|
||||
optionalData1: string;
|
||||
primaryIdentifier: string;
|
||||
secondaryIdentifier: string;
|
||||
};
|
||||
|
||||
export type DataHash = [number, number[]];
|
||||
|
||||
export type PassportData = {
|
||||
mrz: string;
|
||||
signatureAlgorithm: string;
|
||||
pubKey: {modulus?: string, curveName?: string, publicKeyQ?: string};
|
||||
dataGroupHashes: DataHash[];
|
||||
eContent: number[];
|
||||
encryptedDigest: number[];
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
export function checkInputs(
|
||||
passportNumber: string,
|
||||
dateOfBirth: string,
|
||||
dateOfExpiry: string,
|
||||
): boolean {
|
||||
if (passportNumber.length !== 9) {
|
||||
throw new Error('Passport number must be 9 characters long');
|
||||
}
|
||||
if (dateOfBirth.length !== 6) {
|
||||
throw new Error('Date of birth must be 6 characters long');
|
||||
}
|
||||
if (dateOfExpiry.length !== 6) {
|
||||
throw new Error('Date of expiry must be 6 characters long');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function getFirstName(mrz: string): string {
|
||||
const names = mrz.split("<<");
|
||||
const firstName = names[1].split("<")[0].trim();
|
||||
const capitalized = firstName.charAt(0) + firstName.slice(1).toLowerCase();
|
||||
return capitalized || "Unknown";
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import {PassportData} from '../types/passportData';
|
||||
import {
|
||||
arraysAreEqual,
|
||||
assembleEContent,
|
||||
assembleMrz,
|
||||
findTimeOfSignature,
|
||||
formatAndConcatenateDataHashes,
|
||||
formatMrz,
|
||||
} from './utils';
|
||||
import {sha256} from 'js-sha256';
|
||||
|
||||
// hash logic here because the one in utils.ts only works with node
|
||||
export function hash(bytesArray: number[]) {
|
||||
let unsignedBytesArray = bytesArray.map(toUnsignedByte);
|
||||
let hash = sha256(unsignedBytesArray);
|
||||
return hexToSignedBytes(hash);
|
||||
}
|
||||
|
||||
export function hexToSignedBytes(hexString: string) {
|
||||
let bytes = [];
|
||||
for (let i = 0; i < hexString.length - 1; i += 2) {
|
||||
let byte = parseInt(hexString.substr(i, 2), 16);
|
||||
bytes.push(byte >= 128 ? byte - 256 : byte);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
export function toUnsignedByte(signedByte: number) {
|
||||
return signedByte < 0 ? signedByte + 256 : signedByte;
|
||||
}
|
||||
@@ -1,200 +1,8 @@
|
||||
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 getFirstName(mrz: string): string {
|
||||
const names = mrz.split("<<");
|
||||
const firstName = names[1].split("<")[0].trim();
|
||||
const capitalized = firstName.charAt(0) + firstName.slice(1).toLowerCase();
|
||||
return capitalized || "Unknown";
|
||||
}
|
||||
|
||||
export function formatDuration(durationInMs: number) {
|
||||
|
||||
@@ -3604,6 +3604,17 @@ babel-plugin-jest-hoist@^29.5.0:
|
||||
"@types/babel__core" "^7.1.14"
|
||||
"@types/babel__traverse" "^7.0.6"
|
||||
|
||||
babel-plugin-module-resolver@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-module-resolver/-/babel-plugin-module-resolver-5.0.0.tgz#2b7fc176bd55da25f516abf96015617b4f70fc73"
|
||||
integrity sha512-g0u+/ChLSJ5+PzYwLwP8Rp8Rcfowz58TJNCe+L/ui4rpzE/mg//JVX0EWBUYoxaextqnwuGHzfGp2hh0PPV25Q==
|
||||
dependencies:
|
||||
find-babel-config "^2.0.0"
|
||||
glob "^8.0.3"
|
||||
pkg-up "^3.1.0"
|
||||
reselect "^4.1.7"
|
||||
resolve "^1.22.1"
|
||||
|
||||
babel-plugin-polyfill-corejs2@^0.4.4:
|
||||
version "0.4.5"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz#8097b4cb4af5b64a1d11332b6fb72ef5e64a054c"
|
||||
@@ -3767,6 +3778,13 @@ brace-expansion@^1.1.7:
|
||||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
brace-expansion@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
|
||||
integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
|
||||
braces@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
|
||||
@@ -4829,6 +4847,14 @@ finalhandler@1.2.0:
|
||||
statuses "2.0.1"
|
||||
unpipe "~1.0.0"
|
||||
|
||||
find-babel-config@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-2.0.0.tgz#a8216f825415a839d0f23f4d18338a1cc966f701"
|
||||
integrity sha512-dOKT7jvF3hGzlW60Gc3ONox/0rRZ/tz7WCil0bqA1In/3I8f1BctpXahRnEKDySZqci7u+dqq93sZST9fOJpFw==
|
||||
dependencies:
|
||||
json5 "^2.1.1"
|
||||
path-exists "^4.0.0"
|
||||
|
||||
find-cache-dir@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
|
||||
@@ -4930,6 +4956,11 @@ function-bind@^1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
|
||||
|
||||
function-bind@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
|
||||
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
|
||||
|
||||
function.prototype.name@^1.1.5:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621"
|
||||
@@ -5009,6 +5040,17 @@ glob@^7.1.3, glob@^7.1.4:
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@^8.0.3:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e"
|
||||
integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^5.0.1"
|
||||
once "^1.3.0"
|
||||
|
||||
globals@^11.1.0:
|
||||
version "11.12.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
|
||||
@@ -5103,6 +5145,13 @@ has@^1.0.3:
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
hasown@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c"
|
||||
integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==
|
||||
dependencies:
|
||||
function-bind "^1.1.2"
|
||||
|
||||
hermes-estree@0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.12.0.tgz#8a289f9aee854854422345e6995a48613bac2ca8"
|
||||
@@ -5299,6 +5348,13 @@ is-core-module@^2.11.0, is-core-module@^2.9.0:
|
||||
dependencies:
|
||||
has "^1.0.3"
|
||||
|
||||
is-core-module@^2.13.0:
|
||||
version "2.13.1"
|
||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384"
|
||||
integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==
|
||||
dependencies:
|
||||
hasown "^2.0.0"
|
||||
|
||||
is-date-object@^1.0.1:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f"
|
||||
@@ -5977,7 +6033,7 @@ json-stable-stringify-without-jsonify@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
|
||||
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
|
||||
|
||||
json5@^2.2.2:
|
||||
json5@^2.1.1, json5@^2.2.2:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
|
||||
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
|
||||
@@ -6479,6 +6535,13 @@ minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatc
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimatch@^5.0.1:
|
||||
version "5.1.6"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
|
||||
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimist@^1.2.6:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||
@@ -6870,6 +6933,13 @@ pkg-dir@^4.2.0:
|
||||
dependencies:
|
||||
find-up "^4.0.0"
|
||||
|
||||
pkg-up@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5"
|
||||
integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==
|
||||
dependencies:
|
||||
find-up "^3.0.0"
|
||||
|
||||
prelude-ls@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||
@@ -7248,6 +7318,11 @@ require-main-filename@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
|
||||
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
|
||||
|
||||
reselect@^4.1.7:
|
||||
version "4.1.8"
|
||||
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.8.tgz#3f5dc671ea168dccdeb3e141236f69f02eaec524"
|
||||
integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==
|
||||
|
||||
resolve-cwd@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
|
||||
@@ -7284,6 +7359,15 @@ resolve@^1.14.2, resolve@^1.20.0:
|
||||
path-parse "^1.0.7"
|
||||
supports-preserve-symlinks-flag "^1.0.0"
|
||||
|
||||
resolve@^1.22.1:
|
||||
version "1.22.8"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
|
||||
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
|
||||
dependencies:
|
||||
is-core-module "^2.13.0"
|
||||
path-parse "^1.0.7"
|
||||
supports-preserve-symlinks-flag "^1.0.0"
|
||||
|
||||
resolve@^2.0.0-next.4:
|
||||
version "2.0.0-next.4"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.4.tgz#3d37a113d6429f496ec4752d2a2e58efb1fd4660"
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
import * as crypto from 'crypto';
|
||||
import {
|
||||
arraysAreEqual,
|
||||
dataHashesObjToArray,
|
||||
formatMrz,
|
||||
assembleMrz,
|
||||
findTimeOfSignature,
|
||||
parsePubKeyString,
|
||||
formatAndConcatenateDataHashes,
|
||||
assembleEContent,
|
||||
hexToDecimal,
|
||||
bytesToBigDecimal,
|
||||
} from '../utils/utils';
|
||||
} from '../../common/src/utils/utils';
|
||||
import * as forge from 'node-forge';
|
||||
import passportData from '../inputs/passportData_florent.json';
|
||||
import {DataHash} from '../utils/types';
|
||||
import { genSampleData } from '../utils/sampleData';
|
||||
import passportData from '../../common/inputs/passportData.json';
|
||||
import {DataHash} from '../../common/src/utils/types';
|
||||
|
||||
// This script tests the whole flow from MRZ to signature
|
||||
// The passportData is imported from passportData.json written by the server
|
||||
@@ -54,7 +47,7 @@ console.log(
|
||||
// Create the public key
|
||||
const rsa = forge.pki.rsa;
|
||||
const publicKey = rsa.setPublicKey(
|
||||
new forge.jsbn.BigInteger(passportData.modulus, 10),
|
||||
new forge.jsbn.BigInteger(passportData.pubKey.modulus, 10),
|
||||
new forge.jsbn.BigInteger("10001", 16),
|
||||
);
|
||||
|
||||
@@ -63,7 +56,7 @@ const md = forge.md.sha256.create();
|
||||
md.update(forge.util.binary.raw.encode(new Uint8Array(passportData.eContent)));
|
||||
const hashOfEContent = md.digest().getBytes();
|
||||
|
||||
console.log('modulus', passportData.modulus);
|
||||
console.log('modulus', passportData.pubKey.modulus);
|
||||
console.log('eContent', bytesToBigDecimal(passportData.eContent));
|
||||
console.log('signature', bytesToBigDecimal(passportData.encryptedDigest));
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { describe } from 'mocha'
|
||||
import chai, { assert, expect } from 'chai'
|
||||
import chaiAsPromised from 'chai-as-promised'
|
||||
import { arraysAreEqual, bytesToBigDecimal, formatAndConcatenateDataHashes, formatMrz, splitToWords } from '../utils/utils'
|
||||
import { hash, toUnsignedByte, arraysAreEqual, bytesToBigDecimal, formatAndConcatenateDataHashes, formatMrz, splitToWords } from '../../common/src/utils/utils'
|
||||
import { groth16 } from 'snarkjs'
|
||||
import { hash, toUnsignedByte } from '../utils/computeEContent'
|
||||
import { DataHash, PassportData } from '../utils/types'
|
||||
import { genSampleData } from '../utils/sampleData'
|
||||
import { DataHash, PassportData } from '../../common/src/utils/types'
|
||||
import { getPassportData } from '../../common/src/utils/passportData'
|
||||
import { attributeToPosition } from '../../common/src/constants/constants'
|
||||
const fs = require('fs');
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
@@ -17,15 +17,7 @@ describe('Circuit tests', function () {
|
||||
let inputs: any;
|
||||
|
||||
this.beforeAll(async () => {
|
||||
if (fs.existsSync('inputs/passportData.json')) {
|
||||
passportData = require('../inputs/passportData.json');
|
||||
} else {
|
||||
passportData = (await genSampleData()) as PassportData;
|
||||
if (!fs.existsSync("inputs/")) {
|
||||
fs.mkdirSync("inputs/");
|
||||
}
|
||||
fs.writeFileSync('inputs/passportData.json', JSON.stringify(passportData));
|
||||
}
|
||||
const passportData = getPassportData();
|
||||
|
||||
const formattedMrz = formatMrz(passportData.mrz);
|
||||
const mrzHash = hash(formatMrz(passportData.mrz));
|
||||
@@ -51,7 +43,7 @@ describe('Circuit tests', function () {
|
||||
dataHashes: concatenatedDataHashes.map(toUnsignedByte).map(byte => String(byte)),
|
||||
eContentBytes: passportData.eContent.map(toUnsignedByte).map(byte => String(byte)),
|
||||
pubkey: splitToWords(
|
||||
BigInt(passportData.modulus),
|
||||
BigInt(passportData.pubKey.modulus),
|
||||
BigInt(64),
|
||||
BigInt(32)
|
||||
),
|
||||
@@ -60,7 +52,7 @@ describe('Circuit tests', function () {
|
||||
BigInt(64),
|
||||
BigInt(32)
|
||||
),
|
||||
address: "0x9D392187c08fc28A86e1354aD63C70897165b982",
|
||||
address: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
|
||||
}
|
||||
|
||||
})
|
||||
@@ -133,16 +125,6 @@ describe('Circuit tests', function () {
|
||||
})
|
||||
|
||||
describe('Selective disclosure', function() {
|
||||
const attributeToPosition = {
|
||||
issuing_state: [2, 4],
|
||||
name: [5, 43],
|
||||
passport_number: [44, 52],
|
||||
nationality: [54, 56],
|
||||
date_of_birth: [57, 62],
|
||||
gender: [64, 64],
|
||||
expiry_date: [65, 70],
|
||||
}
|
||||
|
||||
const attributeCombinations = [
|
||||
['issuing_state', 'name'],
|
||||
['passport_number', 'nationality', 'date_of_birth'],
|
||||
@@ -150,7 +132,7 @@ describe('Circuit tests', function () {
|
||||
];
|
||||
|
||||
attributeCombinations.forEach(combination => {
|
||||
it.only(`Disclosing ${combination.join(", ")}`, async function () {
|
||||
it(`Disclosing ${combination.join(", ")}`, async function () {
|
||||
const attributeToReveal = Object.keys(attributeToPosition).reduce((acc, attribute) => {
|
||||
acc[attribute] = combination.includes(attribute);
|
||||
return acc;
|
||||
@@ -224,21 +206,3 @@ describe('Circuit tests', function () {
|
||||
})
|
||||
|
||||
|
||||
|
||||
// [
|
||||
// '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
// '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
// '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
// '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
// '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
// '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
// '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
// '\x00', '\x00', '\x00', '\x00', '\x00', 'F', 'R',
|
||||
// 'A', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
// '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
// '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
// '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
|
||||
// '\x00', '\x00', '\x00', '\x00'
|
||||
// ]
|
||||
|
||||
// P<FRADUPONT<<ALPHONSE<HUGUES<ALBERT<<<<<<<<<24HB818324FRA0402111M3111115<<<<<<<<<<<<<<02
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
export function checkInputs(
|
||||
passportNumber: string,
|
||||
dateOfBirth: string,
|
||||
dateOfExpiry: string,
|
||||
): boolean {
|
||||
if (passportNumber.length !== 9) {
|
||||
throw new Error('Passport number must be 9 characters long');
|
||||
}
|
||||
if (dateOfBirth.length !== 6) {
|
||||
throw new Error('Date of birth must be 6 characters long');
|
||||
}
|
||||
if (dateOfExpiry.length !== 6) {
|
||||
throw new Error('Date of expiry must be 6 characters long');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function getFirstName(mrzInfo: any): string {
|
||||
const firstName = mrzInfo.secondaryIdentifier.split('<')[0];
|
||||
return firstName.charAt(0).toUpperCase() + firstName.slice(1).toLowerCase();
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import {sha256} from 'js-sha256';
|
||||
|
||||
// hash logic here because the one in utils.ts only works with node
|
||||
export function hash(bytesArray: number[]) {
|
||||
let unsignedBytesArray = bytesArray.map(toUnsignedByte);
|
||||
let hash = sha256(unsignedBytesArray);
|
||||
return hexToSignedBytes(hash);
|
||||
}
|
||||
|
||||
export function hexToSignedBytes(hexString: string): number[] {
|
||||
let bytes = [];
|
||||
for (let i = 0; i < hexString.length - 1; i += 2) {
|
||||
let byte = parseInt(hexString.substr(i, 2), 16);
|
||||
bytes.push(byte >= 128 ? byte - 256 : byte);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
export function toUnsignedByte(signedByte: number) {
|
||||
return signedByte < 0 ? signedByte + 256 : signedByte;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
export type MrzInfo = {
|
||||
compositeCheckDigit: string;
|
||||
dateOfBirth: string;
|
||||
dateOfBirthCheckDigit: string;
|
||||
dateOfExpiry: string;
|
||||
dateOfExpiryCheckDigit: string;
|
||||
documentCode: string;
|
||||
documentNumber: string;
|
||||
documentNumberCheckDigit: string;
|
||||
documentType: number;
|
||||
gender: string;
|
||||
issuingState: string;
|
||||
nationality: string;
|
||||
optionalData1: string;
|
||||
primaryIdentifier: string;
|
||||
secondaryIdentifier: string;
|
||||
};
|
||||
|
||||
export type DataHash = [number, number[]];
|
||||
|
||||
export type PassportData = {
|
||||
mrz: string;
|
||||
modulus: string;
|
||||
dataGroupHashes: DataHash[];
|
||||
eContent: number[];
|
||||
encryptedDigest: number[];
|
||||
};
|
||||
2
common/.gitignore
vendored
Normal file
2
common/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
inputs
|
||||
node_modules/
|
||||
3
common/README.md
Normal file
3
common/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Proof of Passport Commons
|
||||
|
||||
Constants and utils used in multiple Proof of Passport subdirectories.
|
||||
10
common/package.json
Normal file
10
common/package.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.4",
|
||||
"js-sha256": "^0.10.1",
|
||||
"node-forge": "^1.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node-forge": "^1.3.10"
|
||||
}
|
||||
}
|
||||
9
common/src/constants/constants.ts
Normal file
9
common/src/constants/constants.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export const attributeToPosition = {
|
||||
issuing_state: [2, 4],
|
||||
name: [5, 43],
|
||||
passport_number: [44, 52],
|
||||
nationality: [54, 56],
|
||||
date_of_birth: [57, 62],
|
||||
gender: [64, 64],
|
||||
expiry_date: [65, 70],
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { hash } from "./computeEContent";
|
||||
import { DataHash } from "./types";
|
||||
import { assembleEContent, formatAndConcatenateDataHashes, formatMrz, hexToDecimal } from "./utils";
|
||||
import { DataHash, PassportData } from "./types";
|
||||
import { hash, assembleEContent, formatAndConcatenateDataHashes, formatMrz, hexToDecimal } from "./utils";
|
||||
import * as forge from 'node-forge';
|
||||
const fs = require('fs');
|
||||
|
||||
const sampleMRZ = "P<FRADUPONT<<ALPHONSE<HUGUES<ALBERT<<<<<<<<<24HB818324FRA0402111M3111115<<<<<<<<<<<<<<02"
|
||||
const sampleDataHashes = [
|
||||
@@ -32,7 +32,7 @@ const sampleDataHashes = [
|
||||
]
|
||||
const sampleTimeOfSig = [49, 15, 23, 13, 49, 57, 49, 50, 49, 54, 49, 55, 50, 50, 51, 56, 90]
|
||||
|
||||
export async function genSampleData() {
|
||||
export function genSampleData(): PassportData {
|
||||
const mrzHash = hash(formatMrz(sampleMRZ));
|
||||
sampleDataHashes.unshift([1, mrzHash]);
|
||||
const concatenatedDataHashes = formatAndConcatenateDataHashes(
|
||||
@@ -52,24 +52,38 @@ export async function genSampleData() {
|
||||
md.update(forge.util.binary.raw.encode(new Uint8Array(eContent)));
|
||||
|
||||
const signature = privKey.sign(md)
|
||||
const signatureBytes = Array.from(signature, c => c.charCodeAt(0));
|
||||
const signatureBytes = Array.from(signature, (c: string) => c.charCodeAt(0));
|
||||
|
||||
// Signature verification
|
||||
const hashOfEContent = md.digest().getBytes();
|
||||
const publicKey = rsa.setPublicKey(
|
||||
new forge.jsbn.BigInteger(modulus, 16),
|
||||
new forge.jsbn.BigInteger("10001", 16),
|
||||
);
|
||||
const valid = publicKey.verify(hashOfEContent, signature);
|
||||
console.log('valid ?', valid)
|
||||
// const hashOfEContent = md.digest().getBytes();
|
||||
// const publicKey = rsa.setPublicKey(
|
||||
// new forge.jsbn.BigInteger(modulus, 16),
|
||||
// new forge.jsbn.BigInteger("10001", 16),
|
||||
// );
|
||||
// const valid = publicKey.verify(hashOfEContent, signature);
|
||||
// console.log('valid ?', valid)
|
||||
|
||||
return {
|
||||
"mrz": sampleMRZ,
|
||||
modulus: hexToDecimal(modulus),
|
||||
"dataGroupHashes": sampleDataHashes,
|
||||
"eContent": eContent,
|
||||
"encryptedDigest": signatureBytes,
|
||||
mrz: sampleMRZ,
|
||||
signatureAlgorithm: 'SHA256withRSA', // sha256WithRSAEncryption
|
||||
pubKey: {
|
||||
modulus: hexToDecimal(modulus),
|
||||
},
|
||||
dataGroupHashes: sampleDataHashes as DataHash[],
|
||||
eContent: eContent,
|
||||
encryptedDigest: signatureBytes,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function getPassportData(): PassportData {
|
||||
if (fs.existsSync('../../inputs/passportData.json')) {
|
||||
return require('../../inputs/passportData.json');
|
||||
} else {
|
||||
const sampleData = genSampleData();
|
||||
if (!fs.existsSync('../../inputs/')) {
|
||||
fs.mkdirSync('../../inputs/');
|
||||
}
|
||||
fs.writeFileSync('../../inputs/passportData.json', JSON.stringify(sampleData));
|
||||
return sampleData;
|
||||
}
|
||||
}
|
||||
10
common/src/utils/types.ts
Normal file
10
common/src/utils/types.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export type DataHash = [number, number[]];
|
||||
|
||||
export type PassportData = {
|
||||
mrz: string;
|
||||
signatureAlgorithm: string;
|
||||
pubKey: {modulus?: string, curveName?: string, publicKeyQ?: string};
|
||||
dataGroupHashes: DataHash[];
|
||||
eContent: number[];
|
||||
encryptedDigest: number[];
|
||||
};
|
||||
@@ -1,19 +1,5 @@
|
||||
import {DataHash, MrzInfo, PassportData} from './types';
|
||||
|
||||
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;
|
||||
}
|
||||
import { DataHash } from './types';
|
||||
import {sha256} from 'js-sha256';
|
||||
|
||||
export function dataHashesObjToArray(dataHashes: {
|
||||
[key: string]: number[];
|
||||
@@ -26,27 +12,6 @@ export function dataHashesObjToArray(dataHashes: {
|
||||
.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));
|
||||
|
||||
@@ -62,26 +27,6 @@ export function formatMrz(mrz: string) {
|
||||
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+)/);
|
||||
@@ -160,6 +105,38 @@ export function assembleEContent(
|
||||
return constructedEContent;
|
||||
}
|
||||
|
||||
export function checkInputs(
|
||||
passportNumber: string,
|
||||
dateOfBirth: string,
|
||||
dateOfExpiry: string,
|
||||
): boolean {
|
||||
if (passportNumber.length !== 9) {
|
||||
throw new Error('Passport number must be 9 characters long');
|
||||
}
|
||||
if (dateOfBirth.length !== 6) {
|
||||
throw new Error('Date of birth must be 6 characters long');
|
||||
}
|
||||
if (dateOfExpiry.length !== 6) {
|
||||
throw new Error('Date of expiry must be 6 characters long');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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 const toBinaryString = (byte: any) => {
|
||||
const binary = (parseInt(byte, 10) & 0xFF).toString(2).padStart(8, '0');
|
||||
return binary;
|
||||
@@ -196,4 +173,24 @@ export function bytesToBigDecimal(arr: number[]): string {
|
||||
|
||||
export function hexToDecimal(hex: string): string {
|
||||
return BigInt(`0x${hex}`).toString();
|
||||
}
|
||||
}
|
||||
|
||||
// hash logic here because the one in utils.ts only works with node
|
||||
export function hash(bytesArray: number[]) {
|
||||
let unsignedBytesArray = bytesArray.map(toUnsignedByte);
|
||||
let hash = sha256(unsignedBytesArray);
|
||||
return hexToSignedBytes(hash);
|
||||
}
|
||||
|
||||
export function hexToSignedBytes(hexString: string): number[] {
|
||||
let bytes = [];
|
||||
for (let i = 0; i < hexString.length - 1; i += 2) {
|
||||
let byte = parseInt(hexString.substr(i, 2), 16);
|
||||
bytes.push(byte >= 128 ? byte - 256 : byte);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
export function toUnsignedByte(signedByte: number) {
|
||||
return signedByte < 0 ? signedByte + 256 : signedByte;
|
||||
}
|
||||
8
common/tsconfig.json
Normal file
8
common/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"target": "ES2015",
|
||||
"moduleResolution": "node"
|
||||
}
|
||||
}
|
||||
44
common/yarn.lock
Normal file
44
common/yarn.lock
Normal file
@@ -0,0 +1,44 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@babel/runtime@^7.23.4":
|
||||
version "7.23.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.4.tgz#36fa1d2b36db873d25ec631dcc4923fdc1cf2e2e"
|
||||
integrity sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@types/node-forge@^1.3.10":
|
||||
version "1.3.10"
|
||||
resolved "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.10.tgz"
|
||||
integrity sha512-y6PJDYN4xYBxwd22l+OVH35N+1fCYWiuC3aiP2SlXVE6Lo7SS+rSx9r89hLxrP4pn6n1lBGhHJ12pj3F3Mpttw==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/node@*":
|
||||
version "20.10.0"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-20.10.0.tgz"
|
||||
integrity sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
js-sha256@^0.10.1:
|
||||
version "0.10.1"
|
||||
resolved "https://registry.npmjs.org/js-sha256/-/js-sha256-0.10.1.tgz"
|
||||
integrity sha512-5obBtsz9301ULlsgggLg542s/jqtddfOpV5KJc4hajc9JV8GeY2gZHSVpYBn4nWqAUTJ9v+xwtbJ1mIBgIH5Vw==
|
||||
|
||||
node-forge@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz"
|
||||
integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==
|
||||
|
||||
regenerator-runtime@^0.14.0:
|
||||
version "0.14.0"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45"
|
||||
integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==
|
||||
|
||||
undici-types@~5.26.4:
|
||||
version "5.26.5"
|
||||
resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz"
|
||||
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
|
||||
Reference in New Issue
Block a user