diff --git a/lcov.info b/lcov.info index 6ecd02b..b927ef4 100644 --- a/lcov.info +++ b/lcov.info @@ -1,23 +1,23 @@ TN: SF:src/P256.sol -FN:15,P256.verifySignatureAllowMalleability -FNDA:9,P256.verifySignatureAllowMalleability -DA:22,9 -DA:24,9 -DA:25,9 -BRDA:25,0,0,1 -DA:29,1 -DA:32,8 -DA:35,8 -BRDA:35,1,0,- -BRDA:35,1,1,- -DA:37,8 -FN:44,P256.verifySignature +FN:27,P256.verifySignature FNDA:5,P256.verifySignature -DA:52,5 -BRDA:52,2,0,1 -DA:53,1 -DA:56,4 +DA:35,5 +BRDA:35,0,0,1 +DA:36,1 +DA:39,4 +FN:46,P256.verifySignatureAllowMalleability +FNDA:9,P256.verifySignatureAllowMalleability +DA:53,9 +DA:55,9 +DA:56,9 +BRDA:56,1,0,1 +DA:58,1 +DA:65,8 +DA:68,8 +BRDA:68,2,0,- +BRDA:68,2,1,- +DA:70,8 FNF:2 FNH:2 LF:10 @@ -39,230 +39,227 @@ DA:35,2576 DA:36,2576 DA:38,2576 DA:40,2576 -FN:71,P256Verifier.ecdsa_verify +FN:72,P256Verifier.ecdsa_verify FNDA:2576,P256Verifier.ecdsa_verify -DA:78,2576 -BRDA:78,1,0,297 -DA:79,297 -DA:82,2279 -BRDA:82,2,0,7 -DA:83,7 -DA:86,2272 -DA:88,2272 +DA:79,2576 +BRDA:79,1,0,297 +DA:80,297 +DA:83,2279 +BRDA:83,2,0,7 +DA:84,7 +DA:87,2272 DA:89,2272 -DA:91,2272 -DA:97,2272 -FN:104,P256Verifier.ecAff_isValidPubkey +DA:90,2272 +DA:92,2272 +DA:98,2272 +FN:105,P256Verifier.ecAff_isValidPubkey FNDA:2279,P256Verifier.ecAff_isValidPubkey -DA:108,2279 -BRDA:108,3,0,2 -DA:109,2 -DA:112,2277 -FN:115,P256Verifier.ecAff_satisfiesCurveEqn -FNDA:2277,P256Verifier.ecAff_satisfiesCurveEqn -DA:119,2277 -DA:120,2277 -DA:121,2277 -DA:123,2277 -FN:131,P256Verifier.ecZZ_mulmuladd +DA:109,2279 +BRDA:109,3,0,2 +DA:110,2 +DA:113,2277 +DA:114,2277 +DA:115,2277 +DA:117,2277 +FN:126,P256Verifier.ecZZ_mulmuladd FNDA:2272,P256Verifier.ecZZ_mulmuladd -DA:137,2272 +DA:132,2272 +DA:133,2272 +DA:134,2272 +DA:135,2272 +DA:136,2272 DA:138,2272 -DA:139,2272 -DA:140,2272 DA:141,2272 DA:143,2272 -DA:146,2272 -DA:148,2272 -DA:149,2272 -DA:152,3088 -DA:153,3088 -DA:154,3088 -DA:155,3088 -DA:161,2272 -BRDA:161,6,0,836 -BRDA:161,6,1,725 -DA:162,836 -DA:163,1436 -BRDA:163,7,0,711 -BRDA:163,7,1,725 -DA:164,711 -DA:165,725 -BRDA:165,8,0,725 -DA:166,725 -DA:169,2272 -DA:170,2272 -DA:171,580816 +DA:144,2272 +DA:147,3088 +DA:148,3088 +DA:149,3088 +DA:150,3088 +DA:156,2272 +BRDA:156,6,0,836 +BRDA:156,6,1,725 +DA:157,836 +DA:158,1436 +BRDA:158,7,0,711 +BRDA:158,7,1,725 +DA:159,711 +DA:160,725 +BRDA:160,8,0,725 +DA:161,725 +DA:164,2272 +DA:165,2272 +DA:166,580816 +DA:167,578544 +DA:169,578544 +DA:170,578544 DA:172,578544 -DA:174,578544 -DA:175,578544 -DA:177,578544 -BRDA:177,9,0,148056 -BRDA:177,9,1,143194 -DA:178,148056 -DA:179,430488 -BRDA:179,10,0,147107 -BRDA:179,10,1,143194 -DA:180,147107 -DA:181,283381 -BRDA:181,11,0,140187 -BRDA:181,11,1,143194 -DA:182,140187 -DA:184,143194 -DA:187,430488 -DA:190,2272 -DA:191,2272 -FN:204,P256Verifier.compute_bitpair +BRDA:172,9,0,148056 +BRDA:172,9,1,143194 +DA:173,148056 +DA:174,430488 +BRDA:174,10,0,147107 +BRDA:174,10,1,143194 +DA:175,147107 +DA:176,283381 +BRDA:176,11,0,140187 +BRDA:176,11,1,143194 +DA:177,140187 +DA:179,143194 +DA:182,430488 +DA:185,2272 +DA:186,2272 +FN:199,P256Verifier.compute_bitpair FNDA:581632,P256Verifier.compute_bitpair -DA:205,581632 -FN:212,P256Verifier.ecAff_add +DA:200,581632 +FN:207,P256Verifier.ecAff_add FNDA:2272,P256Verifier.ecAff_add -DA:221,2272 +DA:216,2272 +DA:217,2272 +DA:219,2272 +DA:220,2272 DA:222,2272 DA:224,2272 -DA:225,2272 -DA:227,2272 -DA:229,2272 -FN:236,P256Verifier.ecAff_IsInf +FN:231,P256Verifier.ecAff_IsInf FNDA:437324,P256Verifier.ecAff_IsInf -DA:242,437324 -FN:249,P256Verifier.ecZZ_IsInf +DA:237,437324 +FN:244,P256Verifier.ecZZ_IsInf FNDA:1013576,P256Verifier.ecZZ_IsInf -DA:256,1013576 -FN:266,P256Verifier.ecZZ_dadd_affine +DA:250,1013576 +FN:260,P256Verifier.ecZZ_dadd_affine FNDA:432760,P256Verifier.ecZZ_dadd_affine -DA:274,432760 -BRDA:274,14,0,272 -BRDA:274,14,1,8 -DA:275,1292 -DA:276,272 -DA:277,431468 -BRDA:277,16,0,8 -DA:278,8 -DA:281,431460 -DA:282,431460 -DA:284,431460 -BRDA:284,17,0,431384 -BRDA:284,17,1,56 -DA:286,431384 -DA:287,431384 -DA:288,431384 -DA:289,431384 +DA:268,432760 +BRDA:268,14,0,272 +BRDA:268,14,1,8 +DA:269,1292 +DA:270,272 +DA:271,431468 +BRDA:271,16,0,8 +DA:272,8 +DA:275,431460 +DA:276,431460 +DA:278,431460 +BRDA:278,17,0,431384 +BRDA:278,17,1,56 +DA:280,431384 +DA:281,431384 +DA:282,431384 +DA:283,431384 +DA:284,431384 +DA:285,431384 DA:290,431384 -DA:291,431384 -DA:296,431384 -DA:301,76 -BRDA:301,18,0,20 -BRDA:301,18,1,56 -DA:305,20 -DA:308,56 -DA:311,431460 -FN:319,P256Verifier.ecZZ_double_zz +DA:295,76 +BRDA:295,18,0,20 +BRDA:295,18,1,56 +DA:299,20 +DA:302,56 +DA:305,431460 +FN:313,P256Verifier.ecZZ_double_zz FNDA:578544,P256Verifier.ecZZ_double_zz -DA:321,578544 +DA:315,578544 +DA:317,576456 +DA:318,576456 +DA:319,576456 +DA:320,576456 +DA:321,576456 DA:323,576456 DA:324,576456 DA:325,576456 DA:326,576456 -DA:327,576456 -DA:329,576456 -DA:330,576456 -DA:331,576456 -DA:332,576456 -FN:340,P256Verifier.ecZZ_double_affine +FN:334,P256Verifier.ecZZ_double_affine FNDA:20,P256Verifier.ecZZ_double_affine +DA:336,20 +DA:338,20 +DA:339,20 +DA:340,20 +DA:341,20 DA:342,20 DA:344,20 DA:345,20 -DA:346,20 -DA:347,20 -DA:348,20 -DA:350,20 -DA:351,20 -FN:359,P256Verifier.ecZZ_SetAff +FN:353,P256Verifier.ecZZ_SetAff FNDA:2272,P256Verifier.ecZZ_SetAff -DA:365,2272 -BRDA:365,21,0,16 -DA:366,16 -DA:367,16 -DA:370,2256 +DA:359,2272 +BRDA:359,21,0,16 +DA:360,16 +DA:361,16 +DA:364,2256 +DA:365,2256 +DA:366,2256 DA:371,2256 DA:372,2256 -DA:377,2256 -DA:378,2256 -FN:384,P256Verifier.ecZZ_PointAtInf +FN:378,P256Verifier.ecZZ_PointAtInf FNDA:3164,P256Verifier.ecZZ_PointAtInf -DA:385,3164 -FN:391,P256Verifier.ecAffine_PointAtInf +DA:379,3164 +FN:385,P256Verifier.ecAffine_PointAtInf FNDA:16,P256Verifier.ecAffine_PointAtInf -DA:392,16 -FN:398,P256Verifier.nModInv +DA:386,16 +FN:392,P256Verifier.nModInv FNDA:2272,P256Verifier.nModInv -DA:399,2272 -FN:405,P256Verifier.pModInv +DA:393,2272 +FN:399,P256Verifier.pModInv FNDA:4528,P256Verifier.pModInv -DA:406,4528 -FN:415,P256Verifier.modInv +DA:400,4528 +FN:409,P256Verifier.modInv FNDA:6800,P256Verifier.modInv -DA:422,6800 -DA:423,6800 -BRDA:423,22,0,- -BRDA:423,22,1,- -DA:424,6800 -FNF:18 -FNH:18 -LF:121 -LH:121 +DA:416,6800 +DA:417,6800 +BRDA:417,22,0,- +BRDA:417,22,1,- +DA:418,6800 +FNF:17 +FNH:17 +LF:120 +LH:120 BRF:25 BRH:23 end_of_record TN: SF:src/WebAuthn.sol -FN:13,WebAuthn.startsWith +FN:15,WebAuthn.startsWith FNDA:6,WebAuthn.startsWith -DA:17,6 -DA:18,6 +DA:19,6 DA:20,6 -DA:21,6 +DA:22,6 DA:23,6 -DA:24,209 -BRDA:24,0,0,1 -DA:25,1 -DA:28,208 -BRDA:28,1,0,2 -DA:29,2 -DA:33,3 -FN:44,WebAuthn.checkAuthFlags +DA:25,6 +DA:26,209 +BRDA:26,0,0,1 +DA:27,1 +DA:30,208 +BRDA:30,1,0,2 +DA:31,2 +DA:35,3 +FN:52,WebAuthn.checkAuthFlags FNDA:9,WebAuthn.checkAuthFlags -DA:49,9 -BRDA:49,2,0,1 -DA:50,1 -DA:57,8 -DA:58,5 -DA:59,1 -BRDA:59,3,0,1 -DA:60,1 -DA:65,7 -BRDA:65,4,0,2 -DA:66,2 -BRDA:66,5,0,1 +DA:57,9 +BRDA:57,2,0,1 +DA:58,1 +DA:65,8 +DA:66,5 DA:67,1 -DA:71,6 -FN:124,WebAuthn.verifySignature +BRDA:67,3,0,1 +DA:68,1 +DA:73,7 +BRDA:73,4,0,2 +DA:74,2 +BRDA:74,5,0,1 +DA:75,1 +DA:79,6 +FN:132,WebAuthn.verifySignature FNDA:10,WebAuthn.verifySignature -DA:136,10 -DA:137,9 -DA:138,4 -BRDA:138,6,0,4 -DA:139,4 -DA:145,6 -DA:146,6 -DA:151,6 -BRDA:151,7,0,3 -DA:152,3 -DA:156,3 -DA:157,3 -DA:161,3 +DA:144,10 +DA:145,9 +DA:146,4 +BRDA:146,6,0,4 +DA:147,4 +DA:153,6 +DA:154,6 +DA:159,6 +BRDA:159,7,0,3 +DA:160,3 +DA:164,3 +DA:165,3 +DA:169,3 FNF:3 FNH:3 LF:31 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..d6ceb7d 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: @@ -152,7 +160,7 @@ library WebAuthn { return false; } - // Check that the public key signed sha256(authenticatorData || sha256(clientDataJSON)) + // Signed message: sha256(authenticatorData || sha256(clientDataJSON)) bytes32 clientDataJSONHash = sha256(bytes(clientDataJSON)); bytes32 messageHash = sha256( abi.encodePacked(authenticatorData, clientDataJSONHash)