mirror of
https://github.com/selfxyz/self.git
synced 2026-01-23 13:38:04 -05:00
457
app/App.tsx
457
app/App.tsx
@@ -4,14 +4,10 @@ import {
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
Text,
|
||||
useColorScheme,
|
||||
View,
|
||||
Button,
|
||||
NativeModules,
|
||||
DeviceEventEmitter,
|
||||
TextInput,
|
||||
ActivityIndicator,
|
||||
} from 'react-native';
|
||||
|
||||
import {
|
||||
@@ -21,6 +17,26 @@ import {
|
||||
LearnMoreLinks,
|
||||
ReloadInstructions,
|
||||
} from 'react-native/Libraries/NewAppScreen';
|
||||
import {
|
||||
Text,
|
||||
GluestackUIProvider,
|
||||
Checkbox,
|
||||
CheckboxIndicator,
|
||||
CheckboxIcon,
|
||||
CheckIcon,
|
||||
CheckboxLabel,
|
||||
Input,
|
||||
InputField,
|
||||
ButtonText,
|
||||
ButtonIcon,
|
||||
Button,
|
||||
Spinner,
|
||||
View,
|
||||
ButtonSpinner,
|
||||
} from "@gluestack-ui/themed"
|
||||
import { config } from "@gluestack-ui/config" // Optional if you want to use default theme
|
||||
|
||||
|
||||
// @ts-ignore
|
||||
import PassportReader from 'react-native-passport-reader';
|
||||
import {checkInputs, getFirstName} from './utils/checks';
|
||||
@@ -32,15 +48,25 @@ import {
|
||||
LOCAL_IP,
|
||||
} from '@env';
|
||||
import {DataHash, PassportData} from './types/passportData';
|
||||
import {arraysAreEqual, bytesToBigDecimal, dataHashesObjToArray, formatAndConcatenateDataHashes, formatMrz, splitToWords} from './utils/utils';
|
||||
import {arraysAreEqual, bytesToBigDecimal, dataHashesObjToArray, formatAndConcatenateDataHashes, formatDuration, formatMrz, formatProof, splitToWords} from './utils/utils';
|
||||
import {hash, toUnsignedByte} from './utils/computeEContent';
|
||||
|
||||
console.log('DEFAULT_PNUMBER', DEFAULT_PNUMBER);
|
||||
console.log('LOCAL_IP', LOCAL_IP);
|
||||
|
||||
const CACHE_DATA_IN_LOCAL_SERVER = true;
|
||||
const CACHE_DATA_IN_LOCAL_SERVER = false;
|
||||
const SKIP_SCAN = false;
|
||||
|
||||
const attributeToPosition = {
|
||||
issuing_state: [2, 5],
|
||||
name: [5, 44],
|
||||
passport_number: [44, 52],
|
||||
nationality: [54, 57],
|
||||
date_of_birth: [57, 63],
|
||||
gender: [64, 65],
|
||||
expiry_date: [65, 71],
|
||||
}
|
||||
|
||||
function App(): JSX.Element {
|
||||
const isDarkMode = useColorScheme() === 'dark';
|
||||
const [passportNumber, setPassportNumber] = useState(DEFAULT_PNUMBER ?? '');
|
||||
@@ -49,9 +75,34 @@ function App(): JSX.Element {
|
||||
const [address, setAddress] = useState(DEFAULT_ADDRESS ?? '');
|
||||
const [passportData, setPassportData] = useState<PassportData | null>(null);
|
||||
const [step, setStep] = useState('enterDetails');
|
||||
const [result, setResult] = useState<string>('');
|
||||
const [testResult, setTestResult] = useState<any>(null);
|
||||
const [error, setError] = useState<any>(null);
|
||||
|
||||
const [generatingProof, setGeneratingProof] = useState<boolean>(false);
|
||||
|
||||
const [proofTime, setProofTime] = useState<number>(0);
|
||||
const [totalTime, setTotalTime] = useState<number>(0);
|
||||
const [proofResult, setProofResult] = useState<string>('');
|
||||
|
||||
const [minting, setMinting] = useState<boolean>(false);
|
||||
|
||||
const [disclosure, setDisclosure] = useState({
|
||||
issuing_state: false,
|
||||
name: false,
|
||||
passport_number: false,
|
||||
nationality: false,
|
||||
date_of_birth: false,
|
||||
gender: false,
|
||||
expiry_date: false,
|
||||
});
|
||||
|
||||
const handleDisclosureChange = (field: keyof typeof disclosure) => {
|
||||
setDisclosure(
|
||||
{...disclosure,
|
||||
[field]: !disclosure[field]
|
||||
});
|
||||
};
|
||||
|
||||
const backgroundStyle = {
|
||||
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
|
||||
};
|
||||
@@ -84,7 +135,10 @@ function App(): JSX.Element {
|
||||
async function handleResponse(response: any) {
|
||||
const {
|
||||
mrz,
|
||||
signatureAlgorithm,
|
||||
modulus,
|
||||
curveName,
|
||||
publicKeyQ,
|
||||
dataGroupHashes,
|
||||
eContent,
|
||||
encryptedDigest,
|
||||
@@ -92,14 +146,20 @@ function App(): JSX.Element {
|
||||
|
||||
const passportData: PassportData = {
|
||||
mrz: mrz.replace(/\n/g, ''),
|
||||
modulus: modulus,
|
||||
signatureAlgorithm: signatureAlgorithm,
|
||||
pubKey: {
|
||||
modulus: modulus,
|
||||
curveName: curveName,
|
||||
publicKeyQ: publicKeyQ,
|
||||
},
|
||||
dataGroupHashes: dataHashesObjToArray(JSON.parse(dataGroupHashes)),
|
||||
eContent: JSON.parse(eContent),
|
||||
encryptedDigest: JSON.parse(encryptedDigest),
|
||||
};
|
||||
|
||||
console.log('mrz', passportData.mrz);
|
||||
console.log('modulus', passportData.modulus);
|
||||
console.log('signatureAlgorithm', passportData.signatureAlgorithm);
|
||||
console.log('pubKey', passportData.pubKey);
|
||||
console.log('dataGroupHashes', passportData.dataGroupHashes);
|
||||
console.log('eContent', passportData.eContent);
|
||||
console.log('encryptedDigest', passportData.encryptedDigest);
|
||||
@@ -153,6 +213,9 @@ function App(): JSX.Element {
|
||||
return;
|
||||
}
|
||||
|
||||
setGeneratingProof(true)
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
|
||||
// 1. TODO check signature to make sure the proof will work
|
||||
|
||||
// 2. Format all the data as inputs for the circuit
|
||||
@@ -163,11 +226,27 @@ function App(): JSX.Element {
|
||||
passportData.dataGroupHashes as DataHash[],
|
||||
);
|
||||
|
||||
const reveal_bitmap = Array.from({ length: 88 }, (_, i) => (i >= 16 && i <= 22) ? '1' : '0');
|
||||
|
||||
const reveal_bitmap = Array.from({ length: 88 }, (_) => '0');
|
||||
|
||||
for(const attribute in disclosure) {
|
||||
if (disclosure[attribute as keyof typeof disclosure]) {
|
||||
const [start, end] = attributeToPosition[attribute as keyof typeof attributeToPosition];
|
||||
for(let i = start; i <= end; i++) {
|
||||
reveal_bitmap[i] = '1';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!passportData.pubKey.modulus) {
|
||||
console.log('ECDSA not supported for proof right now.');
|
||||
setError('ECDSA not supported for proof right now.');
|
||||
return;
|
||||
}
|
||||
|
||||
const inputs = {
|
||||
mrz: Array.from(formattedMrz).map(byte => String(byte)),
|
||||
reveal_bitmap: Array.from(reveal_bitmap).map(byte => String(byte)),
|
||||
reveal_bitmap: reveal_bitmap.map(byte => String(byte)),
|
||||
dataHashes: Array.from(concatenatedDataHashes.map(toUnsignedByte)).map(byte => String(byte)),
|
||||
eContentBytes: Array.from(passportData.eContent.map(toUnsignedByte)).map(byte => String(byte)),
|
||||
signature: splitToWords(
|
||||
@@ -176,7 +255,7 @@ function App(): JSX.Element {
|
||||
BigInt(32)
|
||||
),
|
||||
pubkey: splitToWords(
|
||||
BigInt(passportData.modulus),
|
||||
BigInt(passportData.pubKey.modulus),
|
||||
BigInt(64),
|
||||
BigInt(32)
|
||||
),
|
||||
@@ -187,35 +266,42 @@ function App(): JSX.Element {
|
||||
const start = Date.now();
|
||||
NativeModules.RNPassportReader.provePassport(inputs, (err: any, res: any) => {
|
||||
const end = Date.now();
|
||||
setGeneratingProof(false)
|
||||
setStep('proofGenerated');
|
||||
|
||||
if (err) {
|
||||
console.error(err);
|
||||
setProofResult(
|
||||
"res:" + err.toString() + ' time elapsed: ' + (end - start) + 'ms',
|
||||
);
|
||||
} else {
|
||||
console.log(res);
|
||||
setProofResult(
|
||||
"res:" + res.toString() + ' time elapsed: ' + (end - start) + 'ms',
|
||||
setError(
|
||||
"err: " + err.toString(),
|
||||
);
|
||||
return
|
||||
}
|
||||
console.log("res", res);
|
||||
const parsedResponse = JSON.parse(res);
|
||||
console.log('parsedResponse', parsedResponse);
|
||||
console.log('parsedResponse.duration', parsedResponse.duration);
|
||||
|
||||
const deserializedProof = JSON.parse(parsedResponse.serialized_proof);
|
||||
console.log('deserializedProof', deserializedProof);
|
||||
|
||||
const proofFormattedForSolidity = formatProof(deserializedProof);
|
||||
console.log('proofFormattedForSolidity', proofFormattedForSolidity);
|
||||
|
||||
setProofTime(parsedResponse.duration);
|
||||
setTotalTime(end - start);
|
||||
|
||||
setProofResult(JSON.stringify(proofFormattedForSolidity));
|
||||
|
||||
// les outputs publics vont être postés on-chain comment ?
|
||||
});
|
||||
};
|
||||
|
||||
const handleMint = () => {
|
||||
setMinting(true)
|
||||
|
||||
// 5. Format the proof and publicInputs as calldata for the verifier contract
|
||||
// 6. Call the verifier contract with the calldata
|
||||
};
|
||||
|
||||
const callRustLib = async () => {
|
||||
NativeModules.RNPassportReader.callRustLib((err: any, res: any) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
setResult(err.toString());
|
||||
} else {
|
||||
console.log(res); // Should log "5"
|
||||
setResult(res.toString());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const proveRust = async () => {
|
||||
@@ -236,91 +322,230 @@ function App(): JSX.Element {
|
||||
});
|
||||
};
|
||||
|
||||
const handleNative = async () => {
|
||||
const value = await NativeModules.PassportReader.scanPassport('', '', '');
|
||||
console.log(`native tells us ${value}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={backgroundStyle}>
|
||||
<StatusBar
|
||||
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
|
||||
backgroundColor={backgroundStyle.backgroundColor}
|
||||
/>
|
||||
<ScrollView
|
||||
contentInsetAdjustmentBehavior="automatic"
|
||||
style={backgroundStyle}>
|
||||
<View
|
||||
<GluestackUIProvider config={config}>
|
||||
<SafeAreaView style={backgroundStyle}>
|
||||
<StatusBar
|
||||
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
|
||||
backgroundColor={backgroundStyle.backgroundColor}
|
||||
/>
|
||||
<ScrollView
|
||||
contentInsetAdjustmentBehavior="automatic"
|
||||
style={{
|
||||
backgroundColor: isDarkMode ? Colors.black : Colors.white,
|
||||
}}>
|
||||
{step === 'enterDetails' ? (
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.header}>Enter Your Passport Details</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
onChangeText={setPassportNumber}
|
||||
value={passportNumber}
|
||||
placeholder="Passport Number"
|
||||
/>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
onChangeText={setDateOfBirth}
|
||||
value={dateOfBirth}
|
||||
placeholder="Date of Birth (YYMMDD)"
|
||||
/>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
onChangeText={setDateOfExpiry}
|
||||
value={dateOfExpiry}
|
||||
placeholder="Date of Expiry (YYMMDD)"
|
||||
/>
|
||||
<Button title="Scan Passport with NFC" onPress={scan} />
|
||||
<Button title="Call native method" onPress={handleNative} />
|
||||
</View>
|
||||
) : null}
|
||||
{step === 'scanning' ? (
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.header}>Put your phone on your passport</Text>
|
||||
<ActivityIndicator
|
||||
size="large"
|
||||
color="#00ff00"
|
||||
style={{marginTop: 20}}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
{step === 'scanCompleted' ? (
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.header}>Connection successful</Text>
|
||||
<Text style={styles.header}>
|
||||
{passportData && `Hi ${getFirstName(passportData.mrz)} ! `}
|
||||
</Text>
|
||||
<Text style={styles.header}>Input your address or ens</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
onChangeText={setAddress}
|
||||
value={address}
|
||||
placeholder="Your Address or ens name"
|
||||
/>
|
||||
<Button title="Generate zk proof" onPress={handleProve} />
|
||||
</View>
|
||||
) : null}
|
||||
{step === 'proofGenerated' ? (
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.sectionDescription}>Zero-knowledge proof generated</Text>
|
||||
<Text style={styles.header}>You can now mint your SBT</Text>
|
||||
<Button title="Mint Proof of Passport" onPress={handleMint} />
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
<View style={{...styles.sectionContainer, marginTop: 80}}>
|
||||
<Button title="Call rust though java" onPress={callRustLib} />
|
||||
<Text style={styles.sectionDescription}>{result}</Text>
|
||||
<Button title="Test sample proof with arkworks" onPress={proveRust} />
|
||||
<Text style={styles.sectionDescription}>{proofResult}</Text>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
}}
|
||||
>
|
||||
<View>
|
||||
{step === 'enterDetails' ? (
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.header}>Welcome to Proof of Passport</Text>
|
||||
<Text style={{textAlign: "center", fontSize: 20, marginTop: 20, marginBottom: 20}}>Enter Your Passport Details</Text>
|
||||
<Text>Passport Number</Text>
|
||||
<Input
|
||||
variant="outline"
|
||||
size="md"
|
||||
marginBottom={10}
|
||||
marginTop={4}
|
||||
>
|
||||
<InputField
|
||||
value={passportNumber}
|
||||
onChangeText={setPassportNumber}
|
||||
placeholder={DEFAULT_PNUMBER ?? 'Passport Number'}
|
||||
/>
|
||||
</Input>
|
||||
<Text>Date of Birth</Text>
|
||||
<Input
|
||||
variant="outline"
|
||||
size="md"
|
||||
marginBottom={10}
|
||||
marginTop={4}
|
||||
>
|
||||
<InputField
|
||||
value={dateOfBirth}
|
||||
onChangeText={setDateOfBirth}
|
||||
placeholder={DEFAULT_DOB ?? "YYMMDD"}
|
||||
/>
|
||||
</Input>
|
||||
<Text>Date of Expiry</Text>
|
||||
<Input
|
||||
variant="outline"
|
||||
size="md"
|
||||
marginBottom={10}
|
||||
marginTop={4}
|
||||
>
|
||||
<InputField
|
||||
value={dateOfExpiry}
|
||||
onChangeText={setDateOfExpiry}
|
||||
placeholder={DEFAULT_DOE ?? "YYMMDD"}
|
||||
/>
|
||||
</Input>
|
||||
|
||||
<Button
|
||||
onPress={scan}
|
||||
marginTop={10}
|
||||
>
|
||||
<ButtonText>Scan Passport with NFC</ButtonText>
|
||||
{/* <ButtonIcon as={AddIcon} /> */}
|
||||
</Button>
|
||||
</View>
|
||||
) : null}
|
||||
{step === 'scanning' ? (
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.header}>Put your phone on your passport</Text>
|
||||
<Spinner
|
||||
size={60}
|
||||
style={{marginTop: 70}}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
{step === 'scanCompleted' && passportData ? (
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.header}>
|
||||
Hi {getFirstName(passportData.mrz)}
|
||||
</Text>
|
||||
<View
|
||||
marginTop={20}
|
||||
marginBottom={20}
|
||||
>
|
||||
<Text
|
||||
marginBottom={10}
|
||||
>
|
||||
What do you want to disclose ?
|
||||
</Text>
|
||||
{Object.keys(disclosure).map((key) => {
|
||||
const keyy = key as keyof typeof disclosure;
|
||||
const indexes = attributeToPosition[keyy];
|
||||
const keyFormatted = keyy.replace(/_/g, ' ').split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
|
||||
const mrzAttribute = passportData.mrz.slice(indexes[0], indexes[1])
|
||||
const mrzAttributeFormatted = mrzAttribute.replace(/</g, ' ')
|
||||
|
||||
return (
|
||||
<View key={key} margin={2} width={"$full"} flexDirection="row" justifyContent="space-between">
|
||||
<View maxWidth={"$5/6"}>
|
||||
<Text
|
||||
style={{fontWeight: "bold"}}
|
||||
>
|
||||
{keyFormatted}:{" "}
|
||||
</Text>
|
||||
<Text>
|
||||
{mrzAttributeFormatted}
|
||||
</Text>
|
||||
</View>
|
||||
<Checkbox
|
||||
value={key}
|
||||
isChecked={disclosure[keyy]}
|
||||
onChange={() => handleDisclosureChange(keyy)}
|
||||
size="lg"
|
||||
aria-label={key}
|
||||
>
|
||||
<CheckboxIndicator mr="$2">
|
||||
<CheckboxIcon as={CheckIcon} />
|
||||
</CheckboxIndicator>
|
||||
</Checkbox>
|
||||
</View>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
<Text>Enter your address or ens</Text>
|
||||
<Input
|
||||
variant="outline"
|
||||
size="md"
|
||||
marginBottom={10}
|
||||
marginTop={4}
|
||||
>
|
||||
<InputField
|
||||
value={address}
|
||||
onChangeText={setAddress}
|
||||
placeholder="Your Address or ens name"
|
||||
/>
|
||||
</Input>
|
||||
|
||||
{generatingProof ?
|
||||
<Button
|
||||
onPress={handleProve}
|
||||
>
|
||||
<ButtonSpinner mr="$1" />
|
||||
<ButtonText>Generating zk proof</ButtonText>
|
||||
</Button>
|
||||
: <Button
|
||||
onPress={handleProve}
|
||||
>
|
||||
<ButtonText>Generate zk proof</ButtonText>
|
||||
</Button>
|
||||
}
|
||||
</View>
|
||||
) : null}
|
||||
{step === 'proofGenerated' ? (
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.header}>Zero-knowledge proof generated</Text>
|
||||
|
||||
<Text style={{fontWeight: "bold"}}>
|
||||
Proof:
|
||||
</Text>
|
||||
<Text>
|
||||
{proofResult}
|
||||
</Text>
|
||||
|
||||
<Text>
|
||||
<Text style={{ fontWeight: 'bold' }}>Proof Duration:</Text> {formatDuration(proofTime)}
|
||||
</Text>
|
||||
<Text>
|
||||
<Text style={{ fontWeight: 'bold' }}>Total Duration:</Text> {formatDuration(totalTime)}
|
||||
</Text>
|
||||
|
||||
|
||||
{generatingProof ?
|
||||
<Button
|
||||
onPress={handleMint}
|
||||
marginTop={10}
|
||||
>
|
||||
<ButtonSpinner mr="$1" />
|
||||
<ButtonText>Minting Proof of Passport</ButtonText>
|
||||
</Button>
|
||||
: <Button
|
||||
onPress={handleMint}
|
||||
marginTop={10}
|
||||
>
|
||||
<ButtonText>Mint Proof of Passport</ButtonText>
|
||||
</Button>
|
||||
}
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
<View style={{...styles.sectionContainer, marginTop: 80}}>
|
||||
<Text style={{...styles.sectionDescription, textAlign: "center"}}>Test functions</Text>
|
||||
|
||||
<Button
|
||||
onPress={async () => {
|
||||
NativeModules.RNPassportReader.callRustLib((err: any, res: any) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
setTestResult(err);
|
||||
} else {
|
||||
console.log(res); // Should log "5"
|
||||
setTestResult(res);
|
||||
}
|
||||
});
|
||||
}}
|
||||
marginTop={10}
|
||||
>
|
||||
<ButtonText>Call arkworks lib</ButtonText>
|
||||
</Button>
|
||||
{testResult && <Text>{testResult}</Text>}
|
||||
|
||||
<Button
|
||||
onPress={proveRust}
|
||||
marginTop={10}
|
||||
>
|
||||
<ButtonText>Generate sample proof with arkworks</ButtonText>
|
||||
</Button>
|
||||
{proofResult && <Text>{proofResult}</Text>}
|
||||
{error && <Text>{error}</Text>}
|
||||
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</GluestackUIProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -342,17 +567,11 @@ const styles = StyleSheet.create({
|
||||
fontWeight: '700',
|
||||
},
|
||||
header: {
|
||||
fontSize: 24,
|
||||
fontSize: 22,
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
marginTop: 20,
|
||||
},
|
||||
input: {
|
||||
height: 40,
|
||||
margin: 12,
|
||||
borderWidth: 1,
|
||||
padding: 10,
|
||||
},
|
||||
});
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -45,6 +45,9 @@ import org.bouncycastle.asn1.ASN1Primitive
|
||||
import org.bouncycastle.asn1.ASN1Sequence
|
||||
import org.bouncycastle.asn1.ASN1Set
|
||||
import org.bouncycastle.asn1.x509.Certificate
|
||||
import org.bouncycastle.jce.spec.ECNamedCurveSpec
|
||||
import org.bouncycastle.jce.interfaces.ECPublicKey
|
||||
|
||||
|
||||
import org.jmrtd.BACKey
|
||||
import org.jmrtd.BACKeySpec
|
||||
@@ -507,15 +510,40 @@ class RNPassportReaderModule(private val reactContext: ReactApplicationContext)
|
||||
|
||||
val eContentAsn1InputStream = ASN1InputStream(sodFile.eContent.inputStream())
|
||||
val eContentDecomposed: ASN1Primitive = eContentAsn1InputStream.readObject()
|
||||
val rsaPublicKey = sodFile.docSigningCertificate.publicKey as RSAPublicKey
|
||||
|
||||
val passport = Arguments.createMap()
|
||||
passport.putString("mrz", mrzInfo.toString())
|
||||
passport.putString("modulus", rsaPublicKey.modulus.toString())
|
||||
passport.putString("signatureAlgorithm", sodFile.docSigningCertificate.sigAlgName) // this one is new
|
||||
|
||||
val publicKey = sodFile.docSigningCertificate.publicKey
|
||||
if (publicKey is RSAPublicKey) {
|
||||
passport.putString("modulus", publicKey.modulus.toString())
|
||||
} else if (publicKey is ECPublicKey) {
|
||||
// Handle the elliptic curve public key case
|
||||
|
||||
val w = publicKey.getW()
|
||||
passport.putString("publicKeyW", w.toString())
|
||||
|
||||
val ecParams = publicKey.getParams()
|
||||
passport.putInt("cofactor", ecParams.getCofactor())
|
||||
passport.putString("curve", ecParams.getCurve().toString())
|
||||
passport.putString("generator", ecParams.getGenerator().toString())
|
||||
passport.putString("order", ecParams.getOrder().toString())
|
||||
if (ecParams is ECNamedCurveSpec) {
|
||||
passport.putString("curveName", ecParams.getName())
|
||||
}
|
||||
|
||||
// Old one, probably wrong:
|
||||
// passport.putString("curveName", (publicKey.parameters as ECNamedCurveSpec).name)
|
||||
// // passport.putString("curveName", (publicKey.parameters.algorithm)) or maybe this
|
||||
// passport.putString("publicKeyQ", publicKey.q.toString())
|
||||
}
|
||||
|
||||
passport.putString("dataGroupHashes", gson.toJson(sodFile.dataGroupHashes))
|
||||
passport.putString("eContent", gson.toJson(sodFile.eContent))
|
||||
passport.putString("encryptedDigest", gson.toJson(sodFile.encryptedDigest))
|
||||
|
||||
|
||||
|
||||
// Another way to get signing time is to get into signedData.signerInfos, then search for the ICO identifier 1.2.840.113549.1.9.5
|
||||
// passport.putString("signerInfos", gson.toJson(signedData.signerInfos))
|
||||
|
||||
@@ -581,7 +609,7 @@ class RNPassportReaderModule(private val reactContext: ReactApplicationContext)
|
||||
signature: List<String>,
|
||||
pubkey: List<String>,
|
||||
address: String
|
||||
): Int
|
||||
): String
|
||||
|
||||
@ReactMethod
|
||||
fun provePassport(inputs: ReadableMap, callback: Callback) {
|
||||
@@ -594,9 +622,11 @@ class RNPassportReaderModule(private val reactContext: ReactApplicationContext)
|
||||
val signature = inputs.getArray("signature")?.toArrayList()?.map { it as String } ?: listOf()
|
||||
val pubkey = inputs.getArray("pubkey")?.toArrayList()?.map { it as String } ?: listOf()
|
||||
val address = inputs.getString("address") ?: ""
|
||||
|
||||
|
||||
val resultFromProof = provePassport(mrz, reveal_bitmap, data_hashes, e_content_bytes, signature, pubkey, address)
|
||||
|
||||
Log.d(TAG, "resultFromProof: " + resultFromProof.toString())
|
||||
|
||||
// Return the result to JavaScript through the callback
|
||||
callback.invoke(null, resultFromProof)
|
||||
}
|
||||
|
||||
4
app/ark-circom-passport/Cargo.lock
generated
4
app/ark-circom-passport/Cargo.lock
generated
@@ -282,11 +282,15 @@ dependencies = [
|
||||
"ark-bn254",
|
||||
"ark-circom",
|
||||
"ark-crypto-primitives",
|
||||
"ark-ec",
|
||||
"ark-groth16",
|
||||
"ark-std",
|
||||
"color-eyre",
|
||||
"jni",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -16,7 +16,11 @@ ark-bn254 = { version = "=0.4.0" }
|
||||
ark-groth16 = { version = "=0.4.0", default-features = false, features = ["parallel"] }
|
||||
ark-std = { version = "=0.4.0", default-features = false, features = ["parallel"] }
|
||||
ark-crypto-primitives = { version = "=0.4.0" }
|
||||
ark-ec = { version = "=0.4.1" }
|
||||
color-eyre = "=0.6.2"
|
||||
jni = "0.18" # Choose the version that best fits your needs
|
||||
log = "0.4"
|
||||
android_logger = "0.8"
|
||||
android_logger = "0.8"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde_derive = "1.0"
|
||||
|
||||
@@ -5,7 +5,9 @@ use std::os::raw::c_int;
|
||||
|
||||
use ark_bn254::Bn254;
|
||||
use ark_crypto_primitives::snark::SNARK;
|
||||
use ark_groth16::Groth16;
|
||||
use ark_groth16::{Groth16, Proof};
|
||||
// use ark_ff::QuadExtField;
|
||||
use ark_ec::AffineRepr;
|
||||
|
||||
use std::time::Instant;
|
||||
use std::convert::TryInto;
|
||||
@@ -15,21 +17,38 @@ type GrothBn = Groth16<Bn254>;
|
||||
extern crate jni;
|
||||
use jni::objects::{JClass, JObject, JValue, JString};
|
||||
use jni::JNIEnv;
|
||||
use jni::sys::jobject;
|
||||
use jni::sys::jstring;
|
||||
|
||||
use log::Level;
|
||||
use android_logger::Config;
|
||||
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
use serde_json::json;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_io_tradle_nfc_RNPassportReaderModule_callRustCode(
|
||||
env: JNIEnv,
|
||||
_: JClass,
|
||||
) -> jni::sys::jstring {
|
||||
) -> jstring {
|
||||
android_logger::init_once(Config::default().with_min_level(Level::Trace));
|
||||
log::warn!("log before imports");
|
||||
|
||||
let current_dir = std::env::current_dir().unwrap();
|
||||
let path_str = current_dir.to_str().unwrap();
|
||||
let output = env.new_string(path_str).expect("Couldn't create java string!");
|
||||
let my_int: c_int = -1;
|
||||
let my_str: String = "no_proof".to_string();
|
||||
|
||||
let combined = json!({
|
||||
"my_int": my_int,
|
||||
"my_str": my_str
|
||||
});
|
||||
|
||||
let combined_str = combined.to_string();
|
||||
let output = env.new_string(combined_str).expect("Couldn't create java string!");
|
||||
|
||||
output.into_inner()
|
||||
}
|
||||
|
||||
@@ -62,7 +81,7 @@ pub extern "C" fn Java_io_tradle_nfc_RNPassportReaderModule_provePassport(
|
||||
signature: JObject,
|
||||
pubkey: JObject,
|
||||
address: JString,
|
||||
) -> c_int {
|
||||
) -> jstring {
|
||||
|
||||
log::warn!("formatting inputsaaaa...");
|
||||
fn run_proof(
|
||||
@@ -74,7 +93,7 @@ pub extern "C" fn Java_io_tradle_nfc_RNPassportReaderModule_provePassport(
|
||||
pubkey: JObject,
|
||||
address: JString,
|
||||
env: JNIEnv
|
||||
) -> Result<u128, Box<dyn std::error::Error>> {
|
||||
) -> Result<jstring, Box<dyn std::error::Error>> {
|
||||
android_logger::init_once(Config::default().with_min_level(Level::Trace));
|
||||
|
||||
log::warn!("formatting inputs...");
|
||||
@@ -138,6 +157,11 @@ pub extern "C" fn Java_io_tradle_nfc_RNPassportReaderModule_provePassport(
|
||||
let start1 = Instant::now();
|
||||
|
||||
let proof = GrothBn::prove(¶ms, circom, &mut rng)?;
|
||||
|
||||
let proof_str = proof_to_proof_str(&proof);
|
||||
|
||||
let serialized_proof = serde_json::to_string(&proof_str).unwrap();
|
||||
|
||||
let duration1 = start1.elapsed();
|
||||
println!("proof generated. Took: {:?}", duration1);
|
||||
|
||||
@@ -151,7 +175,15 @@ pub extern "C" fn Java_io_tradle_nfc_RNPassportReaderModule_provePassport(
|
||||
|
||||
assert!(verified);
|
||||
|
||||
Ok(duration1.as_millis())
|
||||
let combined = json!({
|
||||
"duration": duration1.as_millis(),
|
||||
"serialized_proof": serialized_proof
|
||||
});
|
||||
|
||||
let combined_str = combined.to_string();
|
||||
let output = env.new_string(combined_str).expect("Couldn't create java string!");
|
||||
|
||||
Ok(output.into_inner())
|
||||
}
|
||||
|
||||
match run_proof(
|
||||
@@ -164,8 +196,8 @@ pub extern "C" fn Java_io_tradle_nfc_RNPassportReaderModule_provePassport(
|
||||
address,
|
||||
env
|
||||
) {
|
||||
Ok(elapsed_millis) => elapsed_millis as i32, // Assuming the elapsed time will fit in an i32
|
||||
Err(_) => -1, // return -1 or some other error code when there's an error
|
||||
Ok(output) => output,
|
||||
Err(_) => env.new_string("error").expect("Couldn't create java string!").into_inner(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +214,29 @@ fn java_arraylist_to_rust_vec(env: &JNIEnv, java_list: JObject) -> Result<Vec<St
|
||||
Ok(vec)
|
||||
}
|
||||
|
||||
// -message:"BpfCoordinator" -message:"MATCH" -message:"maximum" -message:"exception" level:WARN
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Serialize)]
|
||||
struct ProofStr {
|
||||
a: (String, String),
|
||||
b: ((String, String), (String, String)), // Represent each QuadExtField by two BaseFields
|
||||
c: (String, String),
|
||||
}
|
||||
|
||||
// -message:"BpfCoordinator" -message:"MATCH" -message:"maximum" -message:"exception" level:WARN
|
||||
fn proof_to_proof_str(proof: &Proof<Bn254>) -> ProofStr {
|
||||
let a_xy = proof.a.xy().unwrap();
|
||||
let b_xy = proof.b.xy().unwrap();
|
||||
let c_xy = proof.c.xy().unwrap();
|
||||
|
||||
let b_c0_c0 = b_xy.0.c0.to_string();
|
||||
let b_c0_c1 = b_xy.0.c1.to_string();
|
||||
let b_c1_c0 = b_xy.1.c0.to_string();
|
||||
let b_c1_c1 = b_xy.1.c1.to_string();
|
||||
|
||||
ProofStr {
|
||||
a: (a_xy.0.to_string(), a_xy.1.to_string()),
|
||||
b: ((b_c0_c0, b_c0_c1), (b_c1_c0, b_c1_c1)),
|
||||
c: (c_xy.0.to_string(), c_xy.1.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
30
app/countries.md
Normal file
30
app/countries.md
Normal file
@@ -0,0 +1,30 @@
|
||||
Working:
|
||||
- France
|
||||
- Guy from ETH Global Paris (Moldavia ? Bulgaria ?)
|
||||
- Malaysia
|
||||
|
||||
Crashing:
|
||||
- Thailand
|
||||
- Britain
|
||||
|
||||
|
||||
E FATAL EXCEPTION: main
|
||||
Process: com.awesomeproject, PID: 14479
|
||||
java.lang.ClassCastException: org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey cannot be cast to java.security.interfaces.RSAPublicKey
|
||||
at io.tradle.nfc.RNPassportReaderModule$ReadTask.onPostExecute(RNPassportReaderModule.kt:510)
|
||||
at io.tradle.nfc.RNPassportReaderModule$ReadTask.onPostExecute(RNPassportReaderModule.kt:238)
|
||||
at android.os.AsyncTask.finish(AsyncTask.java:771)
|
||||
at android.os.AsyncTask.access$900(AsyncTask.java:199)
|
||||
at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:788)
|
||||
at android.os.Handler.dispatchMessage(Handler.java:106)
|
||||
at android.os.Looper.loopOnce(Looper.java:226)
|
||||
at android.os.Looper.loop(Looper.java:313)
|
||||
at android.app.ActivityThread.main(ActivityThread.java:8751)
|
||||
at java.lang.reflect.Method.invoke(Native Method)
|
||||
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
|
||||
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)
|
||||
2023-10-31 16:41:37.052 625-625 SurfaceFlinger pid-625 E Attempt to update InputPolicyFlags without permission ACCESS_SURFACE_FLINGER
|
||||
2023-10-31 16:41:37.069 625-625 SurfaceFlinger pid-625 E Attempt to update InputPolicyFlags without permission ACCESS_SURFACE_FLINGER
|
||||
2023-10-31 16:41:37.088 625-625 SurfaceFlinger pid-625 E Attempt to update InputPolicyFlags without permission ACCESS_SURFACE_FLINGER
|
||||
2023-10-31 16:41:37.102 625-625 SurfaceFlinger pid-625 E Attempt to update InputPolicyFlags without permission ACCESS_SURFACE_FLINGER
|
||||
2023-10-31 16:41:37.108 1377-2420 TaskStackL...erAbstract pid-1377 E onTaskSnapshotChanged calle
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "AwesomeProject",
|
||||
"name": "proof-of-passport",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -10,6 +10,9 @@
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gluestack-style/react": "^1.0.12",
|
||||
"@gluestack-ui/config": "^1.0.3",
|
||||
"@gluestack-ui/themed": "^1.0.11",
|
||||
"body-parser": "^1.20.2",
|
||||
"buffer": "^6.0.3",
|
||||
"crypto-js": "^4.1.1",
|
||||
@@ -19,7 +22,8 @@
|
||||
"pvutils": "^1.1.3",
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.72.3",
|
||||
"react-native-passport-reader": "^1.0.3"
|
||||
"react-native-passport-reader": "^1.0.3",
|
||||
"react-native-svg": "13.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
|
||||
@@ -20,7 +20,8 @@ export type DataHash = [number, number[]];
|
||||
|
||||
export type PassportData = {
|
||||
mrz: string;
|
||||
modulus: string;
|
||||
signatureAlgorithm: string;
|
||||
pubKey: {modulus?: string, curveName?: string, publicKeyQ?: string};
|
||||
dataGroupHashes: DataHash[];
|
||||
eContent: number[];
|
||||
encryptedDigest: number[];
|
||||
|
||||
@@ -18,5 +18,6 @@ export function checkInputs(
|
||||
export function getFirstName(mrz: string): string {
|
||||
const names = mrz.split("<<");
|
||||
const firstName = names[1].split("<")[0].trim();
|
||||
return firstName || "Unknown";
|
||||
const capitalized = firstName.charAt(0) + firstName.slice(1).toLowerCase();
|
||||
return capitalized || "Unknown";
|
||||
}
|
||||
@@ -195,4 +195,40 @@ export function bytesToBigDecimal(arr: number[]): string {
|
||||
|
||||
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;
|
||||
}
|
||||
1439
app/yarn.lock
1439
app/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -129,15 +129,15 @@ describe('Circuit tests', function () {
|
||||
)).to.be.rejected;
|
||||
})
|
||||
|
||||
it('should support selective disclosure', async function () {
|
||||
it.only('should support selective disclosure', async function () {
|
||||
const attributeToPosition = {
|
||||
issuing_state: [2, 4],
|
||||
name: [5, 43],
|
||||
issuing_state: [2, 5],
|
||||
name: [5, 44],
|
||||
passport_number: [44, 52],
|
||||
nationality: [54, 56],
|
||||
nationality: [54, 57],
|
||||
date_of_birth: [57, 63],
|
||||
gender: [65],
|
||||
expiry_date: [66, 72],
|
||||
gender: [64, 65],
|
||||
expiry_date: [65, 71],
|
||||
}
|
||||
|
||||
const attributeToReveal = {
|
||||
@@ -146,7 +146,7 @@ describe('Circuit tests', function () {
|
||||
passport_number: false,
|
||||
nationality: true,
|
||||
date_of_birth: false,
|
||||
gender:false,
|
||||
gender: false,
|
||||
expiry_date: false,
|
||||
}
|
||||
|
||||
@@ -173,9 +173,10 @@ describe('Circuit tests', function () {
|
||||
)
|
||||
|
||||
console.log('proof done');
|
||||
console.log('proof:', proof);
|
||||
const revealChars = publicSignals.slice(0, 88).map((byte: string) => String.fromCharCode(parseInt(byte, 10)))
|
||||
|
||||
// console.log('revealChars', revealChars)
|
||||
console.log('revealChars', revealChars)
|
||||
|
||||
for(let i = 0; i < revealChars.length; i++) {
|
||||
if (bitmap[i] == '1') {
|
||||
@@ -197,7 +198,7 @@ describe('Circuit tests', function () {
|
||||
}
|
||||
});
|
||||
|
||||
// console.log('reveal', reveal)
|
||||
console.log('reveal', reveal)
|
||||
|
||||
const vKey = JSON.parse(fs.readFileSync("build/verification_key.json"));
|
||||
const verified = await groth16.verify(
|
||||
|
||||
Reference in New Issue
Block a user