From eef1abd638eab1b7306f98349b87d156f370ac35 Mon Sep 17 00:00:00 2001 From: KONFeature Date: Sun, 11 Feb 2024 16:41:47 +0100 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20Add=20the=20options=20to=20upgr?= =?UTF-8?q?ade=20to=20the=20RIP-7212=20p256=20verifier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../webauthn/WebAuthnFclValidator.sol | 57 ++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/src/validator/webauthn/WebAuthnFclValidator.sol b/src/validator/webauthn/WebAuthnFclValidator.sol index 6169ed39..b3302aa3 100644 --- a/src/validator/webauthn/WebAuthnFclValidator.sol +++ b/src/validator/webauthn/WebAuthnFclValidator.sol @@ -22,6 +22,11 @@ struct WebAuthnFclValidatorStorage { /// @notice Using the awesome FreshCryptoLib: https://github.com/rdubois-crypto/FreshCryptoLib/ /// @notice Inspired by the cometh Gnosis Safe signer: https://github.com/cometh-game/p256-signer contract WebAuthnFclValidator is IKernelValidator { + /// @dev Error throwned if someone is trying to update to the precompiled p256 verifier, but it's not yet enable on the current chain + error Rip7212NotavailableOnThisChain(); + /// @dev Error throwned if the pre-compiled p256 verifier is already setup + error Rip7212AlreadyEnabled(); + /// @dev Event emitted when the public key signing the WebAuthN user operation is changed for a given `kernel`. event WebAuthnPublicKeyChanged(address indexed kernel, uint256 x, uint256 y); @@ -30,11 +35,18 @@ contract WebAuthnFclValidator is IKernelValidator { /// @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; + address private immutable P256_VERIFIER; + + /// @dev The address of the pre-compiled p256 verifier + address constant P256_VERIFIER_PRECOMPILED = address(0x100); + + /// @dev The current p256 verifier + address private currentP256Verifier; /// @dev Simple constructor, setting the P256 verifier address constructor(address _p256Verifier) { P256_VERIFIER = _p256Verifier; + currentP256Verifier = _p256Verifier; } /// @dev Disable this validator for a given `kernel` (msg.sender) @@ -99,7 +111,7 @@ contract WebAuthnFclValidator is IKernelValidator { bytes calldata _signature ) private view returns (bool isValid) { return WebAuthnFclVerifier._verifyWebAuthNSignature( - P256_VERIFIER, _hash, _signature, _kernelValidatorStorage.x, _kernelValidatorStorage.y + currentP256Verifier, _hash, _signature, _kernelValidatorStorage.x, _kernelValidatorStorage.y ); } @@ -108,6 +120,23 @@ contract WebAuthnFclValidator is IKernelValidator { revert NotImplemented(); } + /// @dev Update the p256 verifier address if the pre-compiled version is available + function updateToRip7212() external { + // Early exit if already enabled + if (currentP256Verifier == P256_VERIFIER_PRECOMPILED) { + revert Rip7212AlreadyEnabled(); + } + + // Check if it's available, if not exit + if (!isP256PreCompiledAvailable()) { + currentP256Verifier = P256_VERIFIER; + revert Rip7212NotavailableOnThisChain(); + } + + // Update the current p256 verifier + currentP256Verifier = P256_VERIFIER_PRECOMPILED; + } + /* -------------------------------------------------------------------------- */ /* Public view methods */ /* -------------------------------------------------------------------------- */ @@ -121,4 +150,28 @@ contract WebAuthnFclValidator is IKernelValidator { x = kernelValidatorStorage.x; y = kernelValidatorStorage.y; } + + /// @dev Check if the pre-compiled p256 verifier is available on this chain + function isP256PreCompiledAvailable() 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) = P256_VERIFIER_PRECOMPILED.staticcall(testSignatureData); + if (!success) { + return false; + } + + // Decode the result + bytes32 result = abi.decode(data, (bytes32)); + + // Check it's 1 (valid signature) + return result == bytes32(uint256(1)); + } + + /// @dev Get the current p256 verifier address + function getCurrentP256Verifier() public view returns (address) { + return currentP256Verifier; + } } From 4afd3fe452c1768c13f3d85a080ceac05bc1dcc0 Mon Sep 17 00:00:00 2001 From: KONFeature Date: Sun, 11 Feb 2024 18:03:38 +0100 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=90=9B=20Handle=20empty=20response=20?= =?UTF-8?q?from=20the=20pre=20compiled=20p256=20verifier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/validator/webauthn/WebAuthnFclValidator.sol | 17 ++++++++++------- src/validator/webauthn/WebAuthnFclVerifier.sol | 5 ++++- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/validator/webauthn/WebAuthnFclValidator.sol b/src/validator/webauthn/WebAuthnFclValidator.sol index b3302aa3..84e0dd26 100644 --- a/src/validator/webauthn/WebAuthnFclValidator.sol +++ b/src/validator/webauthn/WebAuthnFclValidator.sol @@ -27,6 +27,9 @@ contract WebAuthnFclValidator is IKernelValidator { /// @dev Error throwned if the pre-compiled p256 verifier is already setup error Rip7212AlreadyEnabled(); + /// @dev Event emitted when the p256 verifier is changed + event P256VerifierChanged(address newP256Verifier); + /// @dev Event emitted when the public key signing the WebAuthN user operation is changed for a given `kernel`. event WebAuthnPublicKeyChanged(address indexed kernel, uint256 x, uint256 y); @@ -121,19 +124,19 @@ contract WebAuthnFclValidator is IKernelValidator { } /// @dev Update the p256 verifier address if the pre-compiled version is available - function updateToRip7212() external { + function updateToPrecompiledP256() external { // Early exit if already enabled if (currentP256Verifier == P256_VERIFIER_PRECOMPILED) { revert Rip7212AlreadyEnabled(); } // Check if it's available, if not exit - if (!isP256PreCompiledAvailable()) { - currentP256Verifier = P256_VERIFIER; + if (!isPreCompiledP256Available()) { revert Rip7212NotavailableOnThisChain(); } // Update the current p256 verifier + emit P256VerifierChanged(P256_VERIFIER_PRECOMPILED); currentP256Verifier = P256_VERIFIER_PRECOMPILED; } @@ -152,22 +155,22 @@ contract WebAuthnFclValidator is IKernelValidator { } /// @dev Check if the pre-compiled p256 verifier is available on this chain - function isP256PreCompiledAvailable() public view returns (bool) { + 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) = P256_VERIFIER_PRECOMPILED.staticcall(testSignatureData); - if (!success) { + if (!success || data.length == 0) { return false; } // Decode the result - bytes32 result = abi.decode(data, (bytes32)); + uint256 result = abi.decode(data, (uint256)); // Check it's 1 (valid signature) - return result == bytes32(uint256(1)); + return result == uint256(1); } /// @dev Get the current p256 verifier address diff --git a/src/validator/webauthn/WebAuthnFclVerifier.sol b/src/validator/webauthn/WebAuthnFclVerifier.sol index c89dc75d..6bab86ba 100644 --- a/src/validator/webauthn/WebAuthnFclVerifier.sol +++ b/src/validator/webauthn/WebAuthnFclVerifier.sol @@ -132,7 +132,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; From d8d6f59bb9bed43c21eb18743d47015943483b1d Mon Sep 17 00:00:00 2001 From: KONFeature Date: Wed, 14 Feb 2024 10:30:59 +0100 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=90=9B=20Switch=20between=20pre-compi?= =?UTF-8?q?led=20and=20on=20chain=20p256=20verifier=20via=20a=20signature?= =?UTF-8?q?=20flag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../webauthn/WebAuthnFclValidator.sol | 45 ++----------------- .../webauthn/WebAuthnFclVerifier.sol | 11 ++++- .../validator/WebAuthnFclValidator.t.sol | 11 ++--- 3 files changed, 20 insertions(+), 47 deletions(-) diff --git a/src/validator/webauthn/WebAuthnFclValidator.sol b/src/validator/webauthn/WebAuthnFclValidator.sol index 84e0dd26..fbbc41ed 100644 --- a/src/validator/webauthn/WebAuthnFclValidator.sol +++ b/src/validator/webauthn/WebAuthnFclValidator.sol @@ -22,34 +22,18 @@ struct WebAuthnFclValidatorStorage { /// @notice Using the awesome FreshCryptoLib: https://github.com/rdubois-crypto/FreshCryptoLib/ /// @notice Inspired by the cometh Gnosis Safe signer: https://github.com/cometh-game/p256-signer contract WebAuthnFclValidator is IKernelValidator { - /// @dev Error throwned if someone is trying to update to the precompiled p256 verifier, but it's not yet enable on the current chain - error Rip7212NotavailableOnThisChain(); - /// @dev Error throwned if the pre-compiled p256 verifier is already setup - error Rip7212AlreadyEnabled(); - - /// @dev Event emitted when the p256 verifier is changed - event P256VerifierChanged(address newP256Verifier); - /// @dev Event emitted when the public key signing the WebAuthN user operation is changed for a given `kernel`. event WebAuthnPublicKeyChanged(address indexed kernel, uint256 x, uint256 y); /// @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 + /// @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 The address of the pre-compiled p256 verifier - address constant P256_VERIFIER_PRECOMPILED = address(0x100); - - /// @dev The current p256 verifier - address private currentP256Verifier; - /// @dev Simple constructor, setting the P256 verifier address constructor(address _p256Verifier) { P256_VERIFIER = _p256Verifier; - currentP256Verifier = _p256Verifier; } /// @dev Disable this validator for a given `kernel` (msg.sender) @@ -113,8 +97,9 @@ 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( - currentP256Verifier, _hash, _signature, _kernelValidatorStorage.x, _kernelValidatorStorage.y + P256_VERIFIER, _hash, _signature, _kernelValidatorStorage.x, _kernelValidatorStorage.y ); } @@ -123,23 +108,6 @@ contract WebAuthnFclValidator is IKernelValidator { revert NotImplemented(); } - /// @dev Update the p256 verifier address if the pre-compiled version is available - function updateToPrecompiledP256() external { - // Early exit if already enabled - if (currentP256Verifier == P256_VERIFIER_PRECOMPILED) { - revert Rip7212AlreadyEnabled(); - } - - // Check if it's available, if not exit - if (!isPreCompiledP256Available()) { - revert Rip7212NotavailableOnThisChain(); - } - - // Update the current p256 verifier - emit P256VerifierChanged(P256_VERIFIER_PRECOMPILED); - currentP256Verifier = P256_VERIFIER_PRECOMPILED; - } - /* -------------------------------------------------------------------------- */ /* Public view methods */ /* -------------------------------------------------------------------------- */ @@ -161,7 +129,7 @@ contract WebAuthnFclValidator is IKernelValidator { hex"4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e"; // Perform the static call - (bool success, bytes memory data) = P256_VERIFIER_PRECOMPILED.staticcall(testSignatureData); + (bool success, bytes memory data) = WebAuthnFclVerifier.PRECOMPILED_P256_VERIFIER.staticcall(testSignatureData); if (!success || data.length == 0) { return false; } @@ -172,9 +140,4 @@ contract WebAuthnFclValidator is IKernelValidator { // Check it's 1 (valid signature) return result == uint256(1); } - - /// @dev Get the current p256 verifier address - function getCurrentP256Verifier() public view returns (address) { - return currentP256Verifier; - } } diff --git a/src/validator/webauthn/WebAuthnFclVerifier.sol b/src/validator/webauthn/WebAuthnFclVerifier.sol index 6bab86ba..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); 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));