diff --git a/src/P256.sol b/src/P256.sol index 831850e..cf18e21 100644 --- a/src/P256.sol +++ b/src/P256.sol @@ -8,10 +8,41 @@ pragma solidity 0.8.21; * @custom:security-contact security@daimo.com **/ library P256 { + /// Address of the RIP-7212 precompile address public constant PRECOMPILE = address(0x100); + + /// Address of the fallback P256Verifier contract address public constant VERIFIER = 0xc2b78104907F722DABAc4C69f826a522B2754De4; + /// P256 curve order n/2 for malleability check + uint256 constant P256_N_DIV_2 = + 57896044605178124381348723474703786764998477612067880171211129530534256022184; + + /** + * @dev Verifies a P256 signature. Costs ~3k gas for a valid signature on a + * on a chain with RIP-7212, ~300k otherwise. Treats malleable (s > n/2) + * signatures as invalid. + */ + function verifySignature( + bytes32 message_hash, + uint256 r, + uint256 s, + uint256 x, + uint256 y + ) internal view returns (bool) { + // check for signature malleability + if (s > P256_N_DIV_2) { + return false; + } + + return verifySignatureAllowMalleability(message_hash, r, s, x, y); + } + + /** + * @dev Verifies a P256 signature. Treats malleable (s > n/2) signatures as + * valid, matching the behavior specified by NIST and RIP-7212 exactly. + */ function verifySignatureAllowMalleability( bytes32 message_hash, uint256 r, @@ -23,12 +54,14 @@ library P256 { (bool success, bytes memory ret) = PRECOMPILE.staticcall(args); if (success && ret.length > 0) { - // RIP-7212 precompile returns 1 if signature is valid - // and nothing if signature is invalid, so those fall back to - // more expensive Solidity implementation. + // RIP-7212 precompile returns 1 if signature is valid. return abi.decode(ret, (uint256)) == 1; } + // RIP-7212 is flawed in that it returns no data for an invalid + // signature. This means that "invalid signature" and "missing + // precompile" are not distguishable: both fall back to the more + // expensive Solidity implementation. (bool fallbackSuccess, bytes memory fallbackRet) = VERIFIER.staticcall( args ); @@ -36,23 +69,4 @@ library P256 { return abi.decode(fallbackRet, (uint256)) == 1; } - - /// P256 curve order n/2 for malleability check - uint256 constant P256_N_DIV_2 = - 57896044605178124381348723474703786764998477612067880171211129530534256022184; - - function verifySignature( - bytes32 message_hash, - uint256 r, - uint256 s, - uint256 x, - uint256 y - ) internal view returns (bool) { - // check for signature malleability - if (s > P256_N_DIV_2) { - return false; - } - - return verifySignatureAllowMalleability(message_hash, r, s, x, y); - } } diff --git a/src/P256Verifier.sol b/src/P256Verifier.sol index e0bde14..0dc2e65 100644 --- a/src/P256Verifier.sol +++ b/src/P256Verifier.sol @@ -40,28 +40,29 @@ contract P256Verifier { return abi.encodePacked(ret); } - // Parameters for the sec256r1 (P256) elliptic curve - // Curve prime field modulus + // Parameters for the secp256r1 (P256) elliptic curve + /// P256 curve prime field modulus uint256 private constant p = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; - // Short weierstrass first coefficient + /// Short weierstrass first coefficient uint256 private constant a = // The assumption a == -3 (mod p) is used throughout the codebase 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC; - // Short weierstrass second coefficient + /// Short weierstrass second coefficient uint256 private constant b = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B; - // Generating point affine coordinates + /// Generating point affine x-coordinate uint256 private constant GX = 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296; + /// Generating point affine y-coordinate uint256 private constant GY = 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5; - // Curve order (number of points) + /// Curve order (number of points) uint256 private constant n = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551; - // -2 mod p constant, used to speed up inversion and doubling (avoid negation) + /// -2 mod p constant, used to speed up inversion and doubling (avoid negation) uint256 private constant minus_2modp = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFD; - // -2 mod n constant, used to speed up inversion + /// -2 mod n constant, used to speed up inversion uint256 private constant minus_2modn = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63254F; @@ -98,8 +99,8 @@ contract P256Verifier { } /** - * @dev Check if a point in affine coordinates is on the curve - * Reject 0 point at infinity. + * @dev Check if a point in affine coordinates is on the curve. + * Reject the 0 point at infinity. */ function ecAff_isValidPubkey( uint256 x, @@ -109,13 +110,6 @@ contract P256Verifier { return false; } - return ecAff_satisfiesCurveEqn(x, y); - } - - function ecAff_satisfiesCurveEqn( - uint256 x, - uint256 y - ) internal pure returns (bool) { uint256 LHS = mulmod(y, y, p); // y^2 uint256 RHS = addmod(mulmod(mulmod(x, x, p), x, p), mulmod(a, x, p), p); // x^3 + a x RHS = addmod(RHS, b, p); // x^3 + a*x + b @@ -124,9 +118,10 @@ contract P256Verifier { } /** - * @dev Computation of uG + vQ using Strauss-Shamir's trick, G basepoint, Q public key - * returns tuple of (x coordinate of uG + vQ, boolean that is false if internal precompile staticcall fail) - * Strauss-Shamir is described well in https://stackoverflow.com/a/50994362 + * @dev Computation of uG + vQ using Strauss-Shamir's trick, G basepoint, + * Q public key. Strauss-Shamir is described well in the following post: + * https://stackoverflow.com/a/50994362 + * @return X The x-coordinate of uG + vQ */ function ecZZ_mulmuladd( uint256 QX, @@ -243,8 +238,8 @@ contract P256Verifier { } /** - * @dev Check if a point is the infinity point in ZZ rep. - * Assumes point is on the EC or is the point at infinity. + * @dev Checks if a point is the infinity point in ZZ rep, using only the zz + * and zzz coordinates. Assumes the point is on the curve or at infinity. */ function ecZZ_IsInf( uint256 zz, @@ -252,7 +247,6 @@ contract P256Verifier { ) internal pure returns (bool flag) { // invariant((zz == 0 && zzz == 0) || ecAff_isOnCurve(x, y) for affine // form of the point) - return (zz == 0 && zzz == 0); } diff --git a/src/WebAuthn.sol b/src/WebAuthn.sol index 079ce44..11b9427 100644 --- a/src/WebAuthn.sol +++ b/src/WebAuthn.sol @@ -9,7 +9,9 @@ import "./P256.sol"; * @custom:security-contact security@daimo.com */ library WebAuthn { - /// Checks whether prefix occurs in the beginning of str. + /** + * @dev Checks whether prefix occurs in the beginning of str. + */ function startsWith( string memory prefix, string memory str @@ -33,14 +35,20 @@ library WebAuthn { return true; } - bytes1 private constant AUTH_DATA_FLAGS_UP = 0x01; // Bit 0 - bytes1 private constant AUTH_DATA_FLAGS_UV = 0x04; // Bit 2 - bytes1 private constant AUTH_DATA_FLAGS_BE = 0x08; // Bit 3 - bytes1 private constant AUTH_DATA_FLAGS_BS = 0x10; // Bit 4 + /// Bit mask: authData bit 0, indicating user presence + bytes1 private constant AUTH_DATA_FLAGS_UP = 0x01; + /// Bit mask: authData bit 2, indicating user verification + bytes1 private constant AUTH_DATA_FLAGS_UV = 0x04; + /// Bit mask: authData bit 3, indicating backup eligibility + bytes1 private constant AUTH_DATA_FLAGS_BE = 0x08; + /// Bit mask: authData bit 4, indicating backup state + bytes1 private constant AUTH_DATA_FLAGS_BS = 0x10; - /// Verifies the authFlags in authenticatorData. Numbers in inline comment - /// correspond to the same numbered bullets in - /// https://www.w3.org/TR/webauthn-2/#sctn-verifying-assertion. + /** + * @dev Verifies the authFlags in authenticatorData. Numbers in inline + * comment correspond to the same numbered bullets in the WebAuthn spec: + * https://www.w3.org/TR/webauthn-3/#sctn-authenticator-data + */ function checkAuthFlags( bytes1 flags, bool requireUserVerification @@ -72,8 +80,8 @@ library WebAuthn { } /** - * Verifies a Webauthn P256 signature (Authentication Assertion) as described - * in https://www.w3.org/TR/webauthn-2/#sctn-verifying-assertion. We do not + * @dev Verifies a Webauthn P256 signature (Authentication Assertion). See + * https://www.w3.org/TR/webauthn-2/#sctn-verifying-assertion. We do not * verify all the steps as described in the specification, only ones relevant * to our context. Please carefully read through this list before usage. * Specifically, we do verify the following: