From 10cd5f0e212386aebdd368056f7000cd6ba48009 Mon Sep 17 00:00:00 2001 From: Quentin Nivelais Date: Fri, 16 Feb 2024 16:52:58 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20the=20options=20to=20upgrade?= =?UTF-8?q?=20to=20the=20RIP-7212=20p256=20verifier=20(#82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ Add the options to upgrade to the RIP-7212 p256 verifier * 🐛 Handle empty response from the pre compiled p256 verifier * 🐛 Switch between pre-compiled and on chain p256 verifier via a signature flag --- .../webauthn/WebAuthnFclValidator.sol | 25 ++++++++++++++++--- .../webauthn/WebAuthnFclVerifier.sol | 16 ++++++++++-- .../validator/WebAuthnFclValidator.t.sol | 11 ++++---- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/validator/webauthn/WebAuthnFclValidator.sol b/src/validator/webauthn/WebAuthnFclValidator.sol index 6169ed39..fbbc41ed 100644 --- a/src/validator/webauthn/WebAuthnFclValidator.sol +++ b/src/validator/webauthn/WebAuthnFclValidator.sol @@ -28,9 +28,8 @@ contract WebAuthnFclValidator is IKernelValidator { /// @dev Mapping of kernel address to each webAuthn specific storage mapping(address kernel => WebAuthnFclValidatorStorage webAuthnStorage) private webAuthnValidatorStorage; - /// @dev The address of the p256 verifier contract (should be 0x100 on the RIP-7212 compliant chains) - /// @dev To follow up for the deployment: https://forum.polygon.technology/t/pip-27-precompiled-for-secp256r1-curve-support/13049 - address public immutable P256_VERIFIER; + /// @dev The address of the on-chain p256 verifier contract (will be used if the user want that instead of the pre-compiled one, that way this validator can work on every chain out of the box while rip7212 is slowly being implemented everywhere) + address private immutable P256_VERIFIER; /// @dev Simple constructor, setting the P256 verifier address constructor(address _p256Verifier) { @@ -98,6 +97,7 @@ contract WebAuthnFclValidator is IKernelValidator { bytes32 _hash, bytes calldata _signature ) private view returns (bool isValid) { + // Extract the first byte of the signature to check return WebAuthnFclVerifier._verifyWebAuthNSignature( P256_VERIFIER, _hash, _signature, _kernelValidatorStorage.x, _kernelValidatorStorage.y ); @@ -121,4 +121,23 @@ contract WebAuthnFclValidator is IKernelValidator { x = kernelValidatorStorage.x; y = kernelValidatorStorage.y; } + + /// @dev Check if the pre-compiled p256 verifier is available on this chain + function isPreCompiledP256Available() public view returns (bool) { + // Test signature data, from https://gist.github.com/ulerdogan/8f1714895e23a54147fc529ea30517eb + bytes memory testSignatureData = + hex"4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e"; + + // Perform the static call + (bool success, bytes memory data) = WebAuthnFclVerifier.PRECOMPILED_P256_VERIFIER.staticcall(testSignatureData); + if (!success || data.length == 0) { + return false; + } + + // Decode the result + uint256 result = abi.decode(data, (uint256)); + + // Check it's 1 (valid signature) + return result == uint256(1); + } } diff --git a/src/validator/webauthn/WebAuthnFclVerifier.sol b/src/validator/webauthn/WebAuthnFclVerifier.sol index c89dc75d..02d9aaf1 100644 --- a/src/validator/webauthn/WebAuthnFclVerifier.sol +++ b/src/validator/webauthn/WebAuthnFclVerifier.sol @@ -21,8 +21,12 @@ library WebAuthnFclVerifier { /// @dev Always 0x01 for user presence flag -> https://www.w3.org/TR/webauthn-2/#concept-user-present bytes1 private constant AUTHENTICATOR_DATA_FLAG_MASK = 0x01; + /// @dev The address of the pre-compiled p256 verifier contract (following RIP-7212) + address internal constant PRECOMPILED_P256_VERIFIER = address(0x100); + /// @dev layout of a signature (used to extract the reauired payload from the initial calldata) struct FclSignatureLayout { + bool useOnChainP256Verifier; bytes authenticatorData; bytes clientData; uint256 challengeOffset; @@ -103,7 +107,7 @@ library WebAuthnFclVerifier { } /// @dev Proceed to the full webauth verification - /// @param _p256Verifier The p256 verifier contract + /// @param _p256Verifier The p256 verifier contract on-chain (if user want to use this instead of the precompiled one) /// @param _hash The hash that has been signed via WebAuthN /// @param _signature The signature that has been provided with the userOp /// @param _x The X point of the public key @@ -124,6 +128,11 @@ library WebAuthnFclVerifier { signature := _signature.offset } + // If the signature is using the on-chain p256 verifier, we will use it + if (!signature.useOnChainP256Verifier) { + _p256Verifier = PRECOMPILED_P256_VERIFIER; + } + // Format the webauthn challenge into a p256 message bytes32 challenge = _formatWebAuthNChallenge(_hash, signature); @@ -132,7 +141,10 @@ library WebAuthnFclVerifier { // Send the call the the p256 verifier (bool success, bytes memory ret) = _p256Verifier.staticcall(args); - assert(success); // never reverts, always returns 0 or 1 + // If empty ret, return false + if (success == false || ret.length == 0) { + return false; + } // Ensure that it has returned 1 return abi.decode(ret, (uint256)) == 1; diff --git a/test/foundry/validator/WebAuthnFclValidator.t.sol b/test/foundry/validator/WebAuthnFclValidator.t.sol index 8cd95139..58e1c32b 100644 --- a/test/foundry/validator/WebAuthnFclValidator.t.sol +++ b/test/foundry/validator/WebAuthnFclValidator.t.sol @@ -208,7 +208,7 @@ contract WebAuthnFclValidatorTest is KernelTestBase { uint256[2] memory rs = [type(uint256).max, type(uint256).max]; // Encode all of that into a signature - bytes memory signature = abi.encode(authenticatorData, clientData, clientChallengeDataOffset, rs); + bytes memory signature = abi.encode(true, authenticatorData, clientData, clientChallengeDataOffset, rs); // Check the sig (and ensure we didn't revert here) bool isValid = webAuthNTester.verifySignature(address(p256VerifierWrapper), bytes32(0), signature, x, y); @@ -234,7 +234,7 @@ contract WebAuthnFclValidatorTest is KernelTestBase { uint256[2] memory rs = [r, s]; // Encode all of that into a signature - bytes memory signature = abi.encode(authenticatorData, clientData, clientChallengeDataOffset, rs); + bytes memory signature = abi.encode(true, authenticatorData, clientData, clientChallengeDataOffset, rs); // Ensure the signature is valid bool isValid = webAuthNTester.verifySignature(address(p256VerifierWrapper), _hash, signature, pubX, pubY); @@ -256,7 +256,7 @@ contract WebAuthnFclValidatorTest is KernelTestBase { uint256[2] memory rs = [r, s]; // Encode all of that into a signature - bytes memory signature = abi.encode(authenticatorData, clientData, clientChallengeDataOffset, rs); + bytes memory signature = abi.encode(true, authenticatorData, clientData, clientChallengeDataOffset, rs); // Ensure the signature is valid bool isValid = webAuthNTester.verifySignature(address(p256VerifierWrapper), _hash, signature, pubX, pubY); @@ -281,7 +281,7 @@ contract WebAuthnFclValidatorTest is KernelTestBase { uint256[2] memory rs = [r, s]; // Return the signature - return abi.encode(authenticatorData, clientData, clientChallengeDataOffset, rs); + return abi.encode(true, authenticatorData, clientData, clientChallengeDataOffset, rs); } /// @dev Prepare all the base data needed to perform a webauthn signature o n the given `_hash` @@ -310,6 +310,7 @@ contract WebAuthnFclValidatorTest is KernelTestBase { // Build the signature layout WebAuthnFclVerifier.FclSignatureLayout memory sigLayout = WebAuthnFclVerifier.FclSignatureLayout({ + useOnChainP256Verifier: true, authenticatorData: authenticatorData, clientData: clientData, challengeOffset: clientChallengeDataOffset, @@ -330,7 +331,7 @@ contract WebAuthnFclValidatorTest is KernelTestBase { uint256 constant P256_N_DIV_2 = 57896044605178124381348723474703786764998477612067880171211129530534256022184; /// @dev Generate a p256 signature, from the given `_privateKey` on the given `_hash` - function _getP256Signature(uint256 _privateKey, bytes32 _hash) internal view returns (uint256, uint256) { + function _getP256Signature(uint256 _privateKey, bytes32 _hash) internal pure returns (uint256, uint256) { // Generate the signature using the k value and the private key (bytes32 r, bytes32 s) = vm.signP256(_privateKey, _hash); return (uint256(r), uint256(s));