diff --git a/src/utils/WebAuthn.sol b/src/utils/WebAuthn.sol index fe6896a7b..a37c12cde 100644 --- a/src/utils/WebAuthn.sol +++ b/src/utils/WebAuthn.sol @@ -86,27 +86,23 @@ library WebAuthn { function verify( bytes memory challenge, bool requireUserVerification, - bytes memory authenticatorData, - string memory clientDataJSON, - uint256 challengeIndex, - uint256 typeIndex, - bytes32 r, - bytes32 s, + WebAuthnAuth memory auth, bytes32 x, bytes32 y ) internal view returns (bool result) { - bytes32 h; // The message hash. - string memory b = Base64.encode(challenge, true, true); + bytes32 messageHash; + string memory encoded = Base64.encode(challenge, true, true); /// @solidity memory-safe-assembly assembly { + let clientDataJSON := mload(add(auth, 0x20)) + let n := mload(clientDataJSON) // `clientDataJSON`'s length. + let o := add(clientDataJSON, 0x20) // Start of `clientData`'s bytes. { - let n := mload(clientDataJSON) // `clientDataJSON`'s length. - let o := add(clientDataJSON, 0x20) // Start of `clientData`'s bytes. - let c := challengeIndex - let t := typeIndex - let l := mload(b) // Cache `b`'s length. - let q := add(l, 0x0d) // Length of `b` prefixed with '"challenge":"'. - mstore(b, shr(152, '"challenge":"')) // Temp prefix with '"challenge":"'. + let c := mload(add(auth, 0x40)) // Challenge index in `clientDataJSON`. + let t := mload(add(auth, 0x60)) // Type index in `clientDataJSON`. + let l := mload(encoded) // Cache `encoded`'s length. + let q := add(l, 0x0d) // Length of `encoded` prefixed with '"challenge":"'. + mstore(encoded, shr(152, '"challenge":"')) // Temp prefix with '"challenge":"'. result := and( // 11. Verify JSON's type. Also checks for possible addition overflows. @@ -116,58 +112,54 @@ library WebAuthn { ), // 12. Verify JSON's challenge. Includes a check for the closing '"'. and( - eq(keccak256(add(o, c), q), keccak256(add(b, 0x13), q)), + eq(keccak256(add(o, c), q), keccak256(add(encoded, 0x13), q)), and(eq(byte(0, mload(add(add(o, c), q))), 34), lt(add(q, c), n)) ) ) - mstore(b, l) // Restore `b`'s length, in case of string interning. - h := n // Reuse `h` for `n`, to prevent stack-too-deep. - b := o // Reuse `b` for `o`, to prevent stack-too-deep. + mstore(encoded, l) // Restore `encoded`'s length, in case of string interning. } // Skip 13., 14., 15. - let l := mload(authenticatorData) // Length of `authenticatorData` + let l := mload(mload(auth)) // Length of `authenticatorData`. // 16. Verify that the "User Present" flag is set (bit 0). // 17. Verify that the "User Verified" flag is set (bit 2), if required. // See: https://www.w3.org/TR/webauthn-2/#flags. let u := or(1, shl(2, iszero(iszero(requireUserVerification)))) - // forgefmt: disable-next-item - result := and(and(result, gt(l, 0x20)), - eq(and(byte(0, mload(add(authenticatorData, 0x40))), u), u)) + result := and(and(result, gt(l, 0x20)), eq(and(mload(add(mload(auth), 0x21)), u), u)) if result { - let p := add(authenticatorData, 0x20) // Start of `authenticatorData`'s bytes. + let p := add(mload(auth), 0x20) // Start of `authenticatorData`'s bytes. let e := add(p, l) // Location of the word after `authenticatorData`. let w := mload(e) // Cache the word after `authenticatorData`. // 19. Compute `sha256(clientDataJSON)`. - pop(staticcall(gas(), 2, b, h, e, 0x20)) + pop(staticcall(gas(), 2, o, n, e, 0x20)) // 20. Compute `sha256(authenticatorData ‖ sha256(clientDataJSON))`. pop(staticcall(gas(), 2, p, add(l, 0x20), 0x00, returndatasize())) + mstore(e, w) // Restore the word after `authenticatorData`, in case of reuse. + messageHash := mload(0x00) // `returndatasize()` is `0x20` on `sha256` success, and `0x00` otherwise. if iszero(returndatasize()) { invalid() } - mstore(e, w) // Restore the word after `authenticatorData`, in case of reuse. - h := mload(0x00) } } // `P256.verifySignature` returns false if `s > N/2` due to the malleability check. - if (result) result = P256.verifySignature(h, r, s, x, y); + if (result) result = P256.verifySignature(messageHash, auth.r, auth.s, x, y); } - /// @dev Returns if the `auth` is valid. + /// @dev Plain variant of verify. function verify( bytes memory challenge, bool requireUserVerification, - WebAuthnAuth memory auth, + bytes memory authenticatorData, + string memory clientDataJSON, + uint256 challengeIndex, + uint256 typeIndex, + bytes32 r, + bytes32 s, bytes32 x, bytes32 y ) internal view returns (bool) { return verify( challenge, requireUserVerification, - auth.authenticatorData, - auth.clientDataJSON, - auth.challengeIndex, - auth.typeIndex, - auth.r, - auth.s, + WebAuthnAuth(authenticatorData, clientDataJSON, challengeIndex, typeIndex, r, s), x, y ); diff --git a/src/utils/g/WebAuthn.sol b/src/utils/g/WebAuthn.sol index 10f7db6b9..77b382b2a 100644 --- a/src/utils/g/WebAuthn.sol +++ b/src/utils/g/WebAuthn.sol @@ -90,27 +90,23 @@ library WebAuthn { function verify( bytes memory challenge, bool requireUserVerification, - bytes memory authenticatorData, - string memory clientDataJSON, - uint256 challengeIndex, - uint256 typeIndex, - bytes32 r, - bytes32 s, + WebAuthnAuth memory auth, bytes32 x, bytes32 y ) internal view returns (bool result) { - bytes32 h; // The message hash. - string memory b = Base64.encode(challenge, true, true); + bytes32 messageHash; + string memory encoded = Base64.encode(challenge, true, true); /// @solidity memory-safe-assembly assembly { + let clientDataJSON := mload(add(auth, 0x20)) + let n := mload(clientDataJSON) // `clientDataJSON`'s length. + let o := add(clientDataJSON, 0x20) // Start of `clientData`'s bytes. { - let n := mload(clientDataJSON) // `clientDataJSON`'s length. - let o := add(clientDataJSON, 0x20) // Start of `clientData`'s bytes. - let c := challengeIndex - let t := typeIndex - let l := mload(b) // Cache `b`'s length. - let q := add(l, 0x0d) // Length of `b` prefixed with '"challenge":"'. - mstore(b, shr(152, '"challenge":"')) // Temp prefix with '"challenge":"'. + let c := mload(add(auth, 0x40)) // Challenge index in `clientDataJSON`. + let t := mload(add(auth, 0x60)) // Type index in `clientDataJSON`. + let l := mload(encoded) // Cache `encoded`'s length. + let q := add(l, 0x0d) // Length of `encoded` prefixed with '"challenge":"'. + mstore(encoded, shr(152, '"challenge":"')) // Temp prefix with '"challenge":"'. result := and( // 11. Verify JSON's type. Also checks for possible addition overflows. @@ -120,58 +116,54 @@ library WebAuthn { ), // 12. Verify JSON's challenge. Includes a check for the closing '"'. and( - eq(keccak256(add(o, c), q), keccak256(add(b, 0x13), q)), + eq(keccak256(add(o, c), q), keccak256(add(encoded, 0x13), q)), and(eq(byte(0, mload(add(add(o, c), q))), 34), lt(add(q, c), n)) ) ) - mstore(b, l) // Restore `b`'s length, in case of string interning. - h := n // Reuse `h` for `n`, to prevent stack-too-deep. - b := o // Reuse `b` for `o`, to prevent stack-too-deep. + mstore(encoded, l) // Restore `encoded`'s length, in case of string interning. } // Skip 13., 14., 15. - let l := mload(authenticatorData) // Length of `authenticatorData` + let l := mload(mload(auth)) // Length of `authenticatorData`. // 16. Verify that the "User Present" flag is set (bit 0). // 17. Verify that the "User Verified" flag is set (bit 2), if required. // See: https://www.w3.org/TR/webauthn-2/#flags. let u := or(1, shl(2, iszero(iszero(requireUserVerification)))) - // forgefmt: disable-next-item - result := and(and(result, gt(l, 0x20)), - eq(and(byte(0, mload(add(authenticatorData, 0x40))), u), u)) + result := and(and(result, gt(l, 0x20)), eq(and(mload(add(mload(auth), 0x21)), u), u)) if result { - let p := add(authenticatorData, 0x20) // Start of `authenticatorData`'s bytes. + let p := add(mload(auth), 0x20) // Start of `authenticatorData`'s bytes. let e := add(p, l) // Location of the word after `authenticatorData`. let w := mload(e) // Cache the word after `authenticatorData`. // 19. Compute `sha256(clientDataJSON)`. - pop(staticcall(gas(), 2, b, h, e, 0x20)) + pop(staticcall(gas(), 2, o, n, e, 0x20)) // 20. Compute `sha256(authenticatorData ‖ sha256(clientDataJSON))`. pop(staticcall(gas(), 2, p, add(l, 0x20), 0x00, returndatasize())) + mstore(e, w) // Restore the word after `authenticatorData`, in case of reuse. + messageHash := mload(0x00) // `returndatasize()` is `0x20` on `sha256` success, and `0x00` otherwise. if iszero(returndatasize()) { invalid() } - mstore(e, w) // Restore the word after `authenticatorData`, in case of reuse. - h := mload(0x00) } } // `P256.verifySignature` returns false if `s > N/2` due to the malleability check. - if (result) result = P256.verifySignature(h, r, s, x, y); + if (result) result = P256.verifySignature(messageHash, auth.r, auth.s, x, y); } - /// @dev Returns if the `auth` is valid. + /// @dev Plain variant of verify. function verify( bytes memory challenge, bool requireUserVerification, - WebAuthnAuth memory auth, + bytes memory authenticatorData, + string memory clientDataJSON, + uint256 challengeIndex, + uint256 typeIndex, + bytes32 r, + bytes32 s, bytes32 x, bytes32 y ) internal view returns (bool) { return verify( challenge, requireUserVerification, - auth.authenticatorData, - auth.clientDataJSON, - auth.challengeIndex, - auth.typeIndex, - auth.r, - auth.s, + WebAuthnAuth(authenticatorData, clientDataJSON, challengeIndex, typeIndex, r, s), x, y );