diff --git a/.gitignore b/.gitignore index ea1de09f4..1362dfe37 100644 --- a/.gitignore +++ b/.gitignore @@ -14,12 +14,12 @@ res src/solidity/cache src/solidity/out src/solidity/broadcast +src/solidity/zkpassport-packages/ # Ignore the anvil deployment file src/solidity/deployments/deployment-31337.json -src/solidity/snapshots src/ts/tests/fixtures/zkr/keypairs output-fixtures settings.json src/noir/lib/facematch/src/android/tests.nr -src/noir/bin/facematch/android/**/src/tests.nr \ No newline at end of file +src/noir/bin/facematch/android/**/src/tests.nr diff --git a/src/solidity/run-tests.sh b/src/solidity/run-tests.sh index c63048e80..160bdde85 100755 --- a/src/solidity/run-tests.sh +++ b/src/solidity/run-tests.sh @@ -57,7 +57,7 @@ script/bash/update-roots.sh cd $SCRIPT_DIR # Run the tests -forge test --rpc-url http://localhost:${PORT} -vv +forge test --rpc-url http://localhost:${PORT} -vv "${@:2}" # Kill the anvil process lsof -ti:${PORT} | xargs kill -9 @@ -67,4 +67,4 @@ echo "Tests completed successfully" # If the tests were run based off a fresh clone of the zkpassport-packages repo, remove it if [ "$LOCAL_DIR" = "null" ]; then rm -rf zkpassport-packages -fi \ No newline at end of file +fi diff --git a/src/solidity/snapshots/VerifierTest.json b/src/solidity/snapshots/VerifierTest.json new file mode 100644 index 000000000..0cd5be0cd --- /dev/null +++ b/src/solidity/snapshots/VerifierTest.json @@ -0,0 +1,3 @@ +{ + "UltraHonkVerifier verify": "1828340" +} \ No newline at end of file diff --git a/src/solidity/snapshots/ZKPassportVerifierTest.json b/src/solidity/snapshots/ZKPassportVerifierTest.json new file mode 100644 index 000000000..c9c74a8a3 --- /dev/null +++ b/src/solidity/snapshots/ZKPassportVerifierTest.json @@ -0,0 +1,14 @@ +{ + "ZKPassportVerifier enforceSanctionsRoot": "47917", + "ZKPassportVerifier getBoundData": "75146", + "ZKPassportVerifier getDisclosedData": "40969", + "ZKPassportVerifier isAgeAboveOrEqual": "35853", + "ZKPassportVerifier isBirthdateBeforeOrEqual": "21943", + "ZKPassportVerifier isExpiryDateAfterOrEqual": "33812", + "ZKPassportVerifier isFaceMatchVerified": "32925", + "ZKPassportVerifier isIssuingCountryIn": "206685", + "ZKPassportVerifier isIssuingCountryOut": "204662", + "ZKPassportVerifier isNationalityIn": "203829", + "ZKPassportVerifier test_VerifyAllSubproofsProof1": "1882907", + "ZKPassportVerifier test_VerifyValidProof": "1879814" +} \ No newline at end of file diff --git a/src/solidity/src/ZKPassportVerifier.sol b/src/solidity/src/ZKPassportVerifier.sol index 5f01cfe9c..81c1428b1 100644 --- a/src/solidity/src/ZKPassportVerifier.sol +++ b/src/solidity/src/ZKPassportVerifier.sol @@ -89,7 +89,7 @@ contract ZKPassportVerifier { } function checkDate( - bytes32[] memory publicInputs, + bytes32[] calldata publicInputs, uint256 validityPeriodInSeconds ) internal view returns (bool) { uint256 currentDateTimeStamp = uint256(publicInputs[PublicInput.CURRENT_DATE_INDEX]); @@ -464,7 +464,7 @@ contract ZKPassportVerifier { } function isCountryInOrOut( - string[] memory countryList, + string[] calldata countryList, ProofType proofType, Commitments calldata commitments ) private pure returns (bool) { @@ -487,7 +487,7 @@ contract ZKPassportVerifier { * @return True if the nationality is in the list of countries, false otherwise */ function isNationalityIn( - string[] memory countryList, + string[] calldata countryList, Commitments calldata commitments ) public pure returns (bool) { return isCountryInOrOut(countryList, ProofType.NATIONALITY_INCLUSION, commitments); @@ -500,7 +500,7 @@ contract ZKPassportVerifier { * @return True if the issuing country is in the list of countries, false otherwise */ function isIssuingCountryIn( - string[] memory countryList, + string[] calldata countryList, Commitments calldata commitments ) public pure returns (bool) { return isCountryInOrOut(countryList, ProofType.ISSUING_COUNTRY_INCLUSION, commitments); @@ -514,7 +514,7 @@ contract ZKPassportVerifier { * @return True if the nationality is not in the list of countries, false otherwise */ function isNationalityOut( - string[] memory countryList, + string[] calldata countryList, Commitments calldata commitments ) public pure returns (bool) { return isCountryInOrOut(countryList, ProofType.NATIONALITY_EXCLUSION, commitments); @@ -528,7 +528,7 @@ contract ZKPassportVerifier { * @return True if the issuing country is not in the list of countries, false otherwise */ function isIssuingCountryOut( - string[] memory countryList, + string[] calldata countryList, Commitments calldata commitments ) public pure returns (bool) { return isCountryInOrOut(countryList, ProofType.ISSUING_COUNTRY_EXCLUSION, commitments); @@ -593,30 +593,39 @@ contract ZKPassportVerifier { // What we call scope internally is derived from the domain bytes32 scopeHash = StringUtils.isEmpty(domain) ? bytes32(0) - : sha256(abi.encodePacked(domain)) >> 8; + : sha256(bytes(domain)) >> 8; // What we call the subscope internally is the scope specified // manually in the SDK bytes32 subscopeHash = StringUtils.isEmpty(scope) ? bytes32(0) - : sha256(abi.encodePacked(scope)) >> 8; + : sha256(bytes(scope)) >> 8; return publicInputs[PublicInput.SCOPE_INDEX] == scopeHash && publicInputs[PublicInput.SUBSCOPE_INDEX] == subscopeHash; } function verifyCommittedInputs( - bytes32[] memory paramCommitments, + bytes32[] calldata paramCommitments, Commitments calldata commitments - ) internal pure { + ) internal view { uint256 offset = 0; - for (uint256 i = 0; i < commitments.committedInputCounts.length; i++) { - // One byte is dropped inside the circuit as BN254 is limited to 254 bits - bytes32 calculatedCommitment = sha256( - abi.encodePacked(commitments.committedInputs[offset:offset + commitments.committedInputCounts[i]]) - ) >> 8; + uint[] calldata counts = commitments.committedInputCounts; + bytes calldata inputs = commitments.committedInputs; + for (uint256 i = 0; i < counts.length; i++) { + uint count; + bytes32 calculatedCommitment; + assembly ("memory-safe") { + count := calldataload(add(counts.offset, shl(5, i))) + calldatacopy(mload(0x40), add(inputs.offset, offset), count) + if iszero(staticcall(gas(), 0x02, mload(0x40), count, 0x00, 0x20)) { + revert(0, 0) + } + // One byte is dropped inside the circuit as BN254 is limited to 254 bits + calculatedCommitment := shr(8, mload(0x00)) + } require(calculatedCommitment == paramCommitments[i], "Invalid commitment"); - offset += commitments.committedInputCounts[i]; + offset += count; } // Check that all the committed inputs have been covered, otherwise something is wrong - require(offset == commitments.committedInputs.length, "Invalid committed inputs length"); + require(offset == inputs.length, "Invalid committed inputs length"); } function _getVerifier(bytes32 vkeyHash) internal view returns (address) { diff --git a/src/solidity/src/ultra-honk-verifiers/OuterCount13.sol b/src/solidity/src/ultra-honk-verifiers/OuterCount13.sol index fa30695d4..d6a64ed89 100644 --- a/src/solidity/src/ultra-honk-verifiers/OuterCount13.sol +++ b/src/solidity/src/ultra-honk-verifiers/OuterCount13.sol @@ -711,60 +711,62 @@ library TranscriptLib { // Pairing point object for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) { - p.pairingPointObject[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); + p.pairingPointObject[i] = bytesToFr(proof, boundary); boundary += FIELD_ELEMENT_SIZE; } // Commitments - p.w1 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + p.w1 = bytesToG1Point(proof, boundary); boundary += GROUP_ELEMENT_SIZE; - p.w2 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + p.w2 = bytesToG1Point(proof, boundary); boundary += GROUP_ELEMENT_SIZE; - p.w3 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + p.w3 = bytesToG1Point(proof, boundary); boundary += GROUP_ELEMENT_SIZE; // Lookup / Permutation Helper Commitments - p.lookupReadCounts = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + p.lookupReadCounts = bytesToG1Point(proof, boundary); boundary += GROUP_ELEMENT_SIZE; - p.lookupReadTags = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + p.lookupReadTags = bytesToG1Point(proof, boundary); boundary += GROUP_ELEMENT_SIZE; - p.w4 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + p.w4 = bytesToG1Point(proof, boundary); boundary += GROUP_ELEMENT_SIZE; - p.lookupInverses = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + p.lookupInverses = bytesToG1Point(proof, boundary); boundary += GROUP_ELEMENT_SIZE; - p.zPerm = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + p.zPerm = bytesToG1Point(proof, boundary); boundary += GROUP_ELEMENT_SIZE; // Sumcheck univariates for (uint256 i = 0; i < logN; i++) { for (uint256 j = 0; j < BATCHED_RELATION_PARTIAL_LENGTH; j++) { - p.sumcheckUnivariates[i][j] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); + p.sumcheckUnivariates[i][j] = bytesToFr(proof, boundary); boundary += FIELD_ELEMENT_SIZE; } } // Sumcheck evaluations for (uint256 i = 0; i < NUMBER_OF_ENTITIES; i++) { - p.sumcheckEvaluations[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); + p.sumcheckEvaluations[i] = bytesToFr(proof, boundary); boundary += FIELD_ELEMENT_SIZE; } // Gemini // Read gemini fold univariates for (uint256 i = 0; i < logN - 1; i++) { - p.geminiFoldComms[i] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + p.geminiFoldComms[i] = bytesToG1Point(proof, boundary); boundary += GROUP_ELEMENT_SIZE; } // Read gemini a evaluations for (uint256 i = 0; i < logN; i++) { - p.geminiAEvaluations[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); + p.geminiAEvaluations[i] = bytesToFr(proof, boundary); boundary += FIELD_ELEMENT_SIZE; } // Shplonk - p.shplonkQ = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + p.shplonkQ = bytesToG1Point(proof, boundary); boundary += GROUP_ELEMENT_SIZE; // KZG - p.kzgQuotient = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + p.kzgQuotient = bytesToG1Point(proof, boundary); + + require(boundary + GROUP_ELEMENT_SIZE <= proof.length); } } @@ -1568,6 +1570,12 @@ function bytesToFr(bytes calldata proofSection) pure returns (Fr scalar) { scalar = FrLib.fromBytes32(bytes32(proofSection)); } +function bytesToFr(bytes calldata proof, uint offset) pure returns (Fr scalar) { + assembly { + scalar := mod(calldataload(add(proof.offset, offset)), MODULUS) + } +} + // EC Point utilities function bytesToG1Point(bytes calldata proofSection) pure returns (Honk.G1Point memory point) { point = Honk.G1Point({ @@ -1576,6 +1584,13 @@ function bytesToG1Point(bytes calldata proofSection) pure returns (Honk.G1Point }); } +function bytesToG1Point(bytes calldata proofSection, uint offset) pure returns (Honk.G1Point memory point) { + assembly ("memory-safe") { + mstore(point, mod(calldataload(add(proofSection.offset, offset)), Q)) + mstore(add(point, 0x20), mod(calldataload(add(proofSection.offset, add(offset, 0x20))), Q)) + } +} + function negateInplace(Honk.G1Point memory point) pure returns (Honk.G1Point memory) { point.y = (Q - point.y) % Q; return point; diff --git a/src/solidity/test/ZKPassportVerifier.t.sol b/src/solidity/test/ZKPassportVerifier.t.sol index cd5c0c581..8e054339b 100644 --- a/src/solidity/test/ZKPassportVerifier.t.sol +++ b/src/solidity/test/ZKPassportVerifier.t.sol @@ -61,7 +61,7 @@ contract ZKPassportVerifierTest is TestUtils { committedInputCounts[1] = CommittedInputLen.BIND; // Verify the proof - vm.startSnapshotGas("ZKPassportVerifier verifyProof"); + vm.startSnapshotGas("ZKPassportVerifier test_VerifyValidProof"); vm.warp(CURRENT_DATE); ProofVerificationParams memory params = ProofVerificationParams({ proofVerificationData: ProofVerificationData({ @@ -91,7 +91,10 @@ contract ZKPassportVerifierTest is TestUtils { ); vm.startSnapshotGas("ZKPassportVerifier getDisclosedData"); - DisclosedData memory disclosedData = zkPassportVerifier.getDisclosedData(params.commitments, false); + DisclosedData memory disclosedData = zkPassportVerifier.getDisclosedData( + params.commitments, + false + ); uint256 gasUsedGetDisclosedData = vm.stopSnapshotGas(); console.log("Gas used in ZKPassportVerifier getDisclosedData"); console.log(gasUsedGetDisclosedData); @@ -117,7 +120,7 @@ contract ZKPassportVerifierTest is TestUtils { vm.warp(CURRENT_DATE); ProofVerificationParams memory params = ProofVerificationParams({ proofVerificationData: ProofVerificationData({ - vkeyHash: VKEY_HASH, + vkeyHash: VKEY_HASH, proof: proof, publicInputs: publicInputs }), @@ -140,8 +143,7 @@ contract ZKPassportVerifierTest is TestUtils { ); vm.startSnapshotGas("ZKPassportVerifier getBoundData"); - BoundData memory boundData = zkPassportVerifier - .getBoundData(params.commitments); + BoundData memory boundData = zkPassportVerifier.getBoundData(params.commitments); uint256 gasUsedGetBoundData = vm.stopSnapshotGas(); console.log("Gas used in ZKPassportVerifier getBoundData"); console.log(gasUsedGetBoundData); @@ -150,7 +152,7 @@ contract ZKPassportVerifierTest is TestUtils { assertEq(boundData.customData, "email:test@test.com,customer_id:1234567890"); } - function test_VerifyAllSubproofsProof() public { + function test_VerifyAllSubproofsProof1() public { // Load proof and public inputs from files bytes memory proof = loadBytesFromFile(ALL_SUBPROOFS_PROOF_PATH); bytes32[] memory publicInputs = loadBytes32FromFile(ALL_SUBPROOFS_PUBLIC_INPUTS_PATH); @@ -171,7 +173,7 @@ contract ZKPassportVerifierTest is TestUtils { committedInputCounts[9] = CommittedInputLen.FACEMATCH; // Verify the proof - vm.startSnapshotGas("ZKPassportVerifier verifyProof"); + vm.startSnapshotGas("ZKPassportVerifier test_VerifyAllSubproofsProof1"); vm.warp(CURRENT_DATE); ProofVerificationParams memory params = ProofVerificationParams({ proofVerificationData: ProofVerificationData({ @@ -201,11 +203,10 @@ contract ZKPassportVerifierTest is TestUtils { ); vm.startSnapshotGas("ZKPassportVerifier isAgeAboveOrEqual"); - assertEq(zkPassportVerifier.isAgeAboveOrEqual( - 18, - params.commitments, - params.serviceConfig - ), true); + assertEq( + zkPassportVerifier.isAgeAboveOrEqual(18, params.commitments, params.serviceConfig), + true + ); uint256 gasUsedGetAgeProofInputs = vm.stopSnapshotGas(); console.log("Gas used in ZKPassportVerifier isAgeAboveOrEqual"); console.log(gasUsedGetAgeProofInputs); @@ -216,16 +217,12 @@ contract ZKPassportVerifierTest is TestUtils { countryList[1] = "FRA"; countryList[2] = "USA"; countryList[3] = "GBR"; - bool isNationalityIn = zkPassportVerifier.isNationalityIn( - countryList, - params.commitments - ); + bool isNationalityIn = zkPassportVerifier.isNationalityIn(countryList, params.commitments); uint256 gasUsedGetCountryProofInputs = vm.stopSnapshotGas(); console.log("Gas used in ZKPassportVerifier isNationalityIn"); console.log(gasUsedGetCountryProofInputs); assertEq(isNationalityIn, true); - vm.startSnapshotGas("ZKPassportVerifier isIssuingCountryOut"); string[] memory exclusionCountryList = new string[](3); exclusionCountryList[0] = "ESP"; @@ -282,10 +279,10 @@ contract ZKPassportVerifierTest is TestUtils { vm.startSnapshotGas("ZKPassportVerifier isBirthdateBeforeOrEqual"); bool isBirthdateBeforeOrEqual = zkPassportVerifier.isBirthdateBeforeOrEqual( - PROOF_GENERATION_DATE, - params.commitments, - params.serviceConfig - ); + PROOF_GENERATION_DATE, + params.commitments, + params.serviceConfig + ); uint256 gasUsedIsBirthdateBeforeOrEqual = vm.stopSnapshotGas(); console.log("Gas used in ZKPassportVerifier isBirthdateBeforeOrEqual"); console.log(gasUsedIsBirthdateBeforeOrEqual); @@ -294,10 +291,10 @@ contract ZKPassportVerifierTest is TestUtils { { vm.startSnapshotGas("ZKPassportVerifier isExpiryDateAfterOrEqual"); bool isExpiryDateAfterOrEqual = zkPassportVerifier.isExpiryDateAfterOrEqual( - PROOF_GENERATION_DATE, - params.commitments, - params.serviceConfig - ); + PROOF_GENERATION_DATE, + params.commitments, + params.serviceConfig + ); uint256 gasUsedIsExpiryDateAfterOrEqual = vm.stopSnapshotGas(); console.log("Gas used in ZKPassportVerifier isExpiryDateAfterOrEqual"); console.log(gasUsedIsExpiryDateAfterOrEqual); @@ -315,9 +312,7 @@ contract ZKPassportVerifierTest is TestUtils { params.commitments ); uint256 gasUsedIsIssuingCountryIn = vm.stopSnapshotGas(); - console.log( - "Gas used in ZKPassportVerifier isIssuingCountryIn" - ); + console.log("Gas used in ZKPassportVerifier isIssuingCountryIn"); console.log(gasUsedIsIssuingCountryIn); assertEq(isIssuingCountryIn, true); } @@ -332,17 +327,34 @@ contract ZKPassportVerifierTest is TestUtils { { vm.startSnapshotGas("ZKPassportVerifier isFaceMatchVerified"); - bool isFacematchVerified = zkPassportVerifier.isFaceMatchVerified(FaceMatchMode.REGULAR, OS.IOS, params.commitments); + bool isFacematchVerified = zkPassportVerifier.isFaceMatchVerified( + FaceMatchMode.REGULAR, + OS.IOS, + params.commitments + ); uint256 gasUsedIsFaceMatchVerified = vm.stopSnapshotGas(); console.log("Gas used in ZKPassportVerifier isFaceMatchVerified"); console.log(gasUsedIsFaceMatchVerified); assertEq(isFacematchVerified, true); // Should be false because the facematch mode is not strict but regular - assertEq(zkPassportVerifier.isFaceMatchVerified(FaceMatchMode.STRICT, OS.IOS, params.commitments), false); + assertEq( + zkPassportVerifier.isFaceMatchVerified(FaceMatchMode.STRICT, OS.IOS, params.commitments), + false + ); // Should be false because the OS is not iOS - assertEq(zkPassportVerifier.isFaceMatchVerified(FaceMatchMode.REGULAR, OS.ANDROID, params.commitments), false); + assertEq( + zkPassportVerifier.isFaceMatchVerified( + FaceMatchMode.REGULAR, + OS.ANDROID, + params.commitments + ), + false + ); // Should be true because the OS is any - assertEq(zkPassportVerifier.isFaceMatchVerified(FaceMatchMode.REGULAR, OS.ANY, params.commitments), true); + assertEq( + zkPassportVerifier.isFaceMatchVerified(FaceMatchMode.REGULAR, OS.ANY, params.commitments), + true + ); } } }