From 09249ce2858ec2ddadef35e7b37a864c564a852b Mon Sep 17 00:00:00 2001 From: Nalin Bhardwaj Date: Thu, 16 May 2024 17:04:20 -0700 Subject: [PATCH 1/3] next: enable CI --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3afe3b5..534c185 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [master] + branches: [master, next] pull_request: - branches: [master] + branches: [master, next] env: FOUNDRY_PROFILE: ci From 8ddafd793576524ca9639c9936ce39a0d1a07b25 Mon Sep 17 00:00:00 2001 From: Nalin Date: Sat, 18 May 2024 21:02:38 -0700 Subject: [PATCH 2/3] WebAuthn.sol: simplify contains to startsWith (#41) --- lcov.info | 267 +++++++++++++++++++--------------------- src/WebAuthn.sol | 36 +++--- test/P256Verifier.t.sol | 17 +-- test/WebAuthn.t.sol | 58 +++------ 4 files changed, 169 insertions(+), 209 deletions(-) diff --git a/lcov.info b/lcov.info index 47c8e9a..95863d0 100644 --- a/lcov.info +++ b/lcov.info @@ -428,134 +428,125 @@ BRH:39 end_of_record TN: SF:src/WebAuthn.sol -FN:12,WebAuthn.contains -FNDA:11,WebAuthn.contains -DA:17,11 -DA:17,11 -DA:17,11 -DA:18,11 -DA:18,11 -DA:18,11 -DA:20,11 -DA:20,11 -DA:21,11 -DA:21,11 -DA:23,11 -DA:23,11 -DA:23,176 -DA:23,176 -DA:24,168 -DA:24,168 -DA:24,168 -BRDA:24,0,0,167 -BRDA:24,0,1,1 -DA:25,1 -DA:25,1 -DA:28,167 -DA:28,167 -BRDA:28,1,0,165 -BRDA:28,1,1,2 -DA:29,2 -DA:29,2 -DA:33,8 -DA:33,8 -FN:44,WebAuthn.checkAuthFlags +FN:12,WebAuthn.startsWith +FNDA:6,WebAuthn.startsWith +DA:16,6 +DA:16,6 +DA:16,6 +DA:17,6 +DA:17,6 +DA:17,6 +DA:19,6 +DA:19,6 +DA:20,6 +DA:20,6 +DA:22,6 +DA:22,6 +DA:22,212 +DA:22,212 +DA:23,209 +DA:23,209 +BRDA:23,0,0,208 +BRDA:23,0,1,1 +DA:24,1 +DA:24,1 +DA:27,208 +DA:27,208 +BRDA:27,1,0,206 +BRDA:27,1,1,2 +DA:28,2 +DA:28,2 +DA:32,3 +DA:32,3 +FN:43,WebAuthn.checkAuthFlags FNDA:9,WebAuthn.checkAuthFlags -DA:49,9 -DA:49,9 -DA:49,9 -BRDA:49,2,0,8 -BRDA:49,2,1,1 -DA:50,1 -DA:50,1 -DA:57,8 -DA:57,8 -DA:58,2 -DA:58,2 -BRDA:56,3,0,7 -BRDA:56,3,1,1 -DA:60,1 -DA:60,1 -DA:65,7 -DA:65,7 -DA:65,7 -BRDA:65,4,0,1 -BRDA:65,4,1,1 -DA:66,2 -DA:66,2 -DA:66,2 -BRDA:66,5,0,1 -BRDA:66,5,1,1 -DA:67,1 -DA:67,1 -DA:71,6 -DA:71,6 -FN:124,WebAuthn.verifySignature +DA:48,9 +DA:48,9 +DA:48,9 +BRDA:48,2,0,8 +BRDA:48,2,1,1 +DA:49,1 +DA:49,1 +DA:56,8 +DA:56,8 +DA:57,5 +DA:57,5 +BRDA:55,3,0,7 +BRDA:55,3,1,1 +DA:59,1 +DA:59,1 +DA:64,7 +DA:64,7 +DA:64,7 +BRDA:64,4,0,1 +BRDA:64,4,1,1 +DA:65,2 +DA:65,2 +DA:65,2 +BRDA:65,5,0,1 +BRDA:65,5,1,1 +DA:66,1 +DA:66,1 +DA:70,6 +DA:70,6 +FN:123,WebAuthn.verifySignature FNDA:10,WebAuthn.verifySignature -DA:138,10 -DA:138,10 -DA:138,10 -DA:139,9 -DA:139,9 -BRDA:137,6,0,6 -BRDA:137,6,1,4 -DA:141,4 -DA:141,4 +DA:135,10 +DA:135,10 +DA:135,10 +DA:136,9 +DA:136,9 +BRDA:134,6,0,6 +BRDA:134,6,1,4 +DA:138,4 +DA:138,4 +DA:144,6 +DA:144,6 +DA:144,6 +DA:145,6 DA:145,6 DA:145,6 -DA:146,6 -DA:146,6 -BRDA:146,7,0,5 -BRDA:146,7,1,1 -DA:147,1 -DA:147,1 -DA:151,5 -DA:151,5 -DA:151,5 -DA:152,5 -DA:152,5 -DA:152,5 -DA:158,5 -DA:158,5 -BRDA:158,8,0,3 -BRDA:158,8,1,2 -DA:159,2 -DA:159,2 -DA:163,3 -DA:163,3 -DA:163,3 -DA:164,3 -DA:164,3 -DA:164,3 -DA:168,3 -DA:168,3 -DA:168,3 +DA:150,6 +DA:150,6 +BRDA:150,7,0,3 +BRDA:150,7,1,3 +DA:151,3 +DA:151,3 +DA:155,3 +DA:155,3 +DA:155,3 +DA:156,3 +DA:156,3 +DA:156,3 +DA:160,3 +DA:160,3 +DA:160,3 FNF:3 FNH:3 -LF:32 -LH:32 -BRF:18 -BRH:18 +LF:29 +LH:29 +BRF:16 +BRH:16 end_of_record TN: SF:src/utils/Base64URL.sol FN:7,Base64URL.encode -FNDA:1799,Base64URL.encode -DA:8,1799 -DA:8,1799 -DA:8,1799 -DA:9,1799 -DA:9,1799 -DA:9,1799 -DA:12,1799 -DA:12,1799 -DA:13,1799 -DA:13,1799 -DA:13,1799 -DA:13,1795 -BRDA:13,0,0,518 +FNDA:1800,Base64URL.encode +DA:8,1800 +DA:8,1800 +DA:8,1800 +DA:9,1800 +DA:9,1800 +DA:9,1800 +DA:12,1800 +DA:12,1800 +DA:13,1800 +DA:13,1800 +DA:13,1800 +DA:13,1796 +BRDA:13,0,0,519 BRDA:13,0,1,1281 -DA:13,518 +DA:13,519 DA:14,1281 DA:14,1281 DA:14,1281 @@ -563,33 +554,33 @@ DA:14,1277 BRDA:14,1,0,1281 BRDA:14,1,1,511 DA:14,511 -DA:16,1799 -DA:16,1799 -DA:16,1799 -DA:17,1799 -DA:17,1799 -DA:17,1799 -DA:19,1799 -DA:19,1799 -DA:19,526644 -DA:19,526644 -DA:20,524845 -DA:20,524845 +DA:16,1800 +DA:16,1800 +DA:16,1800 +DA:17,1800 +DA:17,1800 +DA:17,1800 +DA:19,1800 +DA:19,1800 +DA:19,526651 +DA:19,526651 +DA:20,524851 +DA:20,524851 BRDA:20,2,0,1248 -BRDA:20,2,1,523597 +BRDA:20,2,1,523603 DA:21,1248 DA:21,1248 -DA:22,523597 -DA:22,523597 +DA:22,523603 +DA:22,523603 BRDA:22,3,0,176191 -BRDA:22,3,1,347406 +BRDA:22,3,1,347412 DA:23,176191 DA:23,176191 -DA:25,347406 -DA:25,347406 -DA:29,1799 -DA:29,1799 -DA:29,1799 +DA:25,347412 +DA:25,347412 +DA:29,1800 +DA:29,1800 +DA:29,1800 FNF:1 FNH:1 LF:14 diff --git a/src/WebAuthn.sol b/src/WebAuthn.sol index e616eb3..b7fefbd 100644 --- a/src/WebAuthn.sol +++ b/src/WebAuthn.sol @@ -8,24 +8,23 @@ import "./P256.sol"; * Helper library for external contracts to verify WebAuthn signatures. **/ library WebAuthn { - /// Checks whether substr occurs in str starting at a given byte offset. - function contains( - string memory substr, - string memory str, - uint256 location + /// Checks whether prefix occurs in the beginning of str. + function startsWith( + string memory prefix, + string memory str ) internal pure returns (bool) { - bytes memory substrBytes = bytes(substr); + bytes memory prefixBytes = bytes(prefix); bytes memory strBytes = bytes(str); - uint256 substrLen = substrBytes.length; + uint256 prefixLen = prefixBytes.length; uint256 strLen = strBytes.length; - for (uint256 i = 0; i < substrLen; i++) { - if (location + i >= strLen) { + for (uint256 i = 0; i < prefixLen; i++) { + if (i >= strLen) { return false; } - if (substrBytes[i] != strBytes[location + i]) { + if (prefixBytes[i] != strBytes[i]) { return false; } } @@ -126,8 +125,6 @@ library WebAuthn { bytes memory authenticatorData, bool requireUserVerification, string memory clientDataJSON, - uint256 challengeLocation, - uint256 responseTypeLocation, uint256 r, uint256 s, uint256 x, @@ -142,20 +139,15 @@ library WebAuthn { } // Check that response is for an authentication assertion - string memory responseType = '"type":"webauthn.get"'; - if (!contains(responseType, clientDataJSON, responseTypeLocation)) { - return false; - } - - // Check that challenge is in the clientDataJSON + // and that the challenge is in the clientDataJSON + // as per https://www.w3.org/TR/webauthn-2/#clientdatajson-serialization string memory challengeB64url = Base64URL.encode(challenge); - string memory challengeProperty = string.concat( - '"challenge":"', + string memory prefix = string.concat( + '{"type":"webauthn.get","challenge":"', challengeB64url, '"' ); - - if (!contains(challengeProperty, clientDataJSON, challengeLocation)) { + if (!startsWith(prefix, clientDataJSON)) { return false; } diff --git a/test/P256Verifier.t.sol b/test/P256Verifier.t.sol index 0147691..3a11c48 100644 --- a/test/P256Verifier.t.sol +++ b/test/P256Verifier.t.sol @@ -123,35 +123,38 @@ contract P256VerifierTest is Test { // In-bounds dummy key (1, 1) // Calls modexp, which takes gas. - (bool result, uint gasUsed) = evaluate(hash, r, s, x, y); - console2.log("gasUsed ", gasUsed); + (bool result, uint gasUsedWithModexp) = evaluate(hash, r, s, x, y); + console2.log("gasUsed ", gasUsedWithModexp); assertEq(result, false); - assertGt(gasUsed, 1500); + assertGt(gasUsedWithModexp, 5000); + + uint failFastGasBound = (gasUsedWithModexp * 9) / 10; // Out-of-bounds public key. Fails fast, takes less gas. (x, y) = (0, 1); + uint gasUsed; (result, gasUsed) = evaluate(hash, r, s, x, y); console2.log("gasUsed ", gasUsed); assertEq(result, false); - assertLt(gasUsed, 1500); + assertLt(gasUsed, failFastGasBound); (x, y) = (1, 0); (result, gasUsed) = evaluate(hash, r, s, x, y); console2.log("gasUsed ", gasUsed); assertEq(result, false); - assertLt(gasUsed, 1500); + assertLt(gasUsed, failFastGasBound); (x, y) = (1, p); (result, gasUsed) = evaluate(hash, r, s, x, y); console2.log("gasUsed ", gasUsed); assertEq(result, false); - assertLt(gasUsed, 1500); + assertLt(gasUsed, failFastGasBound); (x, y) = (p, 1); (result, gasUsed) = evaluate(hash, r, s, x, y); console2.log("gasUsed ", gasUsed); assertEq(result, false); - assertLt(gasUsed, 1500); + assertLt(gasUsed, failFastGasBound); // p-1 is in-bounds but point is not on curve. (x, y) = (p - 1, 1); diff --git a/test/WebAuthn.t.sol b/test/WebAuthn.t.sol index 23912ca..270278c 100644 --- a/test/WebAuthn.t.sol +++ b/test/WebAuthn.t.sol @@ -21,8 +21,6 @@ contract WebAuthnTest is Test { 0x32e005a53ae49a96ac88c715243638dd5c985fbd463c727d8eefd05bee4e2570; uint256 s = 0x7a4fef4d0b11187f95f69eefbb428df8ac799bbd9305066b1e9c9fe9a5bcf8c4; - uint256 challengeLocation = 23; - uint256 responseTypeLocation = 1; function setUp() public { // Deploy P256 Verifier @@ -36,8 +34,6 @@ contract WebAuthnTest is Test { authenticatorData: authenticatorData, requireUserVerification: false, clientDataJSON: clientDataJSON, - challengeLocation: challengeLocation, - responseTypeLocation: responseTypeLocation, r: r, s: s, x: publicKey[0], @@ -46,21 +42,16 @@ contract WebAuthnTest is Test { assertTrue(ret); } - // Test failures in contains() function - function testContains() public { - bool ret; - uint256 customChallengeLocation = challengeLocation; - uint256 customResponseTypeLocation = responseTypeLocation; - - // Wrong challengeLocation - customChallengeLocation = 50; - ret = WebAuthn.verifySignature({ + // Test startsWith is implemented correctly + function testStartsWith() public { + // End too early + string + memory customClientDataJSON = '{"type":"webauthn.get","challenge":'; + bool ret = WebAuthn.verifySignature({ challenge: challenge, authenticatorData: authenticatorData, - requireUserVerification: false, - clientDataJSON: clientDataJSON, - challengeLocation: customChallengeLocation, - responseTypeLocation: customResponseTypeLocation, + requireUserVerification: true, + clientDataJSON: customClientDataJSON, r: r, s: s, x: publicKey[0], @@ -68,15 +59,13 @@ contract WebAuthnTest is Test { }); assertFalse(ret); - // challengeLocation out of bounds of string - customChallengeLocation = 100; + // missing { in beginning + customClientDataJSON = '"type":"webauthn.get","challenge":"dGVzdA","origin":"https://funny-froyo-3f9b75.netlify.app"}'; ret = WebAuthn.verifySignature({ challenge: challenge, authenticatorData: authenticatorData, - requireUserVerification: false, - clientDataJSON: clientDataJSON, - challengeLocation: customChallengeLocation, - responseTypeLocation: customResponseTypeLocation, + requireUserVerification: true, + clientDataJSON: customClientDataJSON, r: r, s: s, x: publicKey[0], @@ -84,16 +73,13 @@ contract WebAuthnTest is Test { }); assertFalse(ret); - // Wrong responseTypeLocation - customChallengeLocation = 23; - customResponseTypeLocation = 0; + // missing closing quote on challenge + customClientDataJSON = '{"type":"webauthn.get","challenge":"dGVzdA,"origin":"https://funny-froyo-3f9b75.netlify.app"}'; ret = WebAuthn.verifySignature({ challenge: challenge, authenticatorData: authenticatorData, - requireUserVerification: false, - clientDataJSON: clientDataJSON, - challengeLocation: customChallengeLocation, - responseTypeLocation: customResponseTypeLocation, + requireUserVerification: true, + clientDataJSON: customClientDataJSON, r: r, s: s, x: publicKey[0], @@ -114,8 +100,6 @@ contract WebAuthnTest is Test { authenticatorData: customAuthenticatorData, requireUserVerification: false, clientDataJSON: clientDataJSON, - challengeLocation: challengeLocation, - responseTypeLocation: responseTypeLocation, r: r, s: s, x: publicKey[0], @@ -130,8 +114,6 @@ contract WebAuthnTest is Test { authenticatorData: customAuthenticatorData, requireUserVerification: false, clientDataJSON: clientDataJSON, - challengeLocation: challengeLocation, - responseTypeLocation: responseTypeLocation, r: r, s: s, x: publicKey[0], @@ -147,8 +129,6 @@ contract WebAuthnTest is Test { authenticatorData: customAuthenticatorData, requireUserVerification: true, clientDataJSON: clientDataJSON, - challengeLocation: challengeLocation, - responseTypeLocation: responseTypeLocation, r: r, s: s, x: publicKey[0], @@ -165,8 +145,6 @@ contract WebAuthnTest is Test { authenticatorData: customAuthenticatorData, requireUserVerification: false, clientDataJSON: clientDataJSON, - challengeLocation: challengeLocation, - responseTypeLocation: responseTypeLocation, r: r, s: s, x: publicKey[0], @@ -183,8 +161,6 @@ contract WebAuthnTest is Test { authenticatorData: customAuthenticatorData, requireUserVerification: false, clientDataJSON: clientDataJSON, - challengeLocation: challengeLocation, - responseTypeLocation: responseTypeLocation, r: r, s: s, x: publicKey[0], @@ -201,8 +177,6 @@ contract WebAuthnTest is Test { authenticatorData: authenticatorData, requireUserVerification: true, clientDataJSON: clientDataJSON, - challengeLocation: challengeLocation, - responseTypeLocation: responseTypeLocation, r: r, s: s, x: publicKey[0], From 8279636382f72278b5a02c138b4dd924634cdf2c Mon Sep 17 00:00:00 2001 From: Nalin Date: Sat, 18 May 2024 22:03:39 -0700 Subject: [PATCH 3/3] P256.sol: support precompile as primary, use solidity as fallback (#43) * P256.sol: support precompile as primary, use solidity as fallback * P256.sol: add fallback test --- lcov.info | 661 ++++++++++++++++++++++++++---------------------- src/P256.sol | 20 +- test/P256.t.sol | 59 +++++ 3 files changed, 428 insertions(+), 312 deletions(-) diff --git a/lcov.info b/lcov.info index 95863d0..0796ed4 100644 --- a/lcov.info +++ b/lcov.info @@ -1,264 +1,276 @@ TN: SF:src/P256.sol -FN:10,P256.verifySignatureAllowMalleability -FNDA:6,P256.verifySignatureAllowMalleability -DA:17,6 -DA:17,6 -DA:17,6 -DA:18,6 -DA:18,6 -DA:18,6 -DA:19,6 -DA:19,6 -BRDA:19,0,0,- -BRDA:19,0,1,- -DA:21,6 -DA:21,6 -DA:21,6 -DA:21,6 -FN:28,P256.verifySignature +FN:13,P256.verifySignatureAllowMalleability +FNDA:9,P256.verifySignatureAllowMalleability +DA:20,9 +DA:20,9 +DA:20,9 +DA:22,9 +DA:22,9 +DA:22,9 +DA:23,9 +DA:23,9 +DA:23,9 +BRDA:23,0,0,8 +BRDA:23,0,1,1 +DA:27,1 +DA:27,1 +DA:27,1 +DA:27,1 +DA:30,8 +DA:30,8 +DA:30,8 +DA:33,8 +DA:33,8 +BRDA:33,1,0,- +BRDA:33,1,1,- +DA:35,8 +DA:35,8 +DA:35,8 +DA:35,8 +FN:42,P256.verifySignature FNDA:5,P256.verifySignature -DA:36,5 -DA:36,5 -BRDA:36,1,0,4 -BRDA:36,1,1,1 -DA:37,1 -DA:37,1 -DA:40,4 -DA:40,4 -DA:40,4 +DA:50,5 +DA:50,5 +BRDA:50,2,0,4 +BRDA:50,2,1,1 +DA:51,1 +DA:51,1 +DA:54,4 +DA:54,4 +DA:54,4 FNF:2 FNH:2 -LF:7 -LH:7 -BRF:4 -BRH:2 +LF:10 +LH:10 +BRF:6 +BRH:4 end_of_record TN: SF:src/P256Verifier.sol FN:26,P256Verifier. -FNDA:2573,P256Verifier. -DA:27,2573 -DA:27,2573 -BRDA:27,0,0,2572 +FNDA:2577,P256Verifier. +DA:27,2577 +DA:27,2577 +BRDA:27,0,0,2576 BRDA:27,0,1,1 DA:28,1 DA:28,1 DA:28,1 -DA:31,2572 -DA:31,2572 -DA:31,2572 -DA:32,2572 -DA:32,2572 -DA:32,2572 -DA:33,2572 -DA:33,2572 -DA:33,2572 -DA:34,2572 -DA:34,2572 -DA:34,2572 -DA:35,2572 -DA:35,2572 -DA:35,2572 -DA:37,2572 -DA:37,2572 -DA:37,2572 -DA:39,2572 -DA:39,2572 -DA:39,2572 +DA:31,2576 +DA:31,2576 +DA:31,2576 +DA:32,2576 +DA:32,2576 +DA:32,2576 +DA:33,2576 +DA:33,2576 +DA:33,2576 +DA:34,2576 +DA:34,2576 +DA:34,2576 +DA:35,2576 +DA:35,2576 +DA:35,2576 +DA:37,2576 +DA:37,2576 +DA:37,2576 +DA:39,2576 +DA:39,2576 +DA:39,2576 FN:70,P256Verifier.ecdsa_verify -FNDA:2572,P256Verifier.ecdsa_verify -DA:77,2572 -DA:77,2572 -DA:77,2572 -DA:77,2572 -DA:77,2572 -DA:77,2543 -DA:77,2339 -DA:77,2327 -BRDA:77,1,0,2275 +FNDA:2576,P256Verifier.ecdsa_verify +DA:77,2576 +DA:77,2576 +DA:77,2576 +DA:77,2576 +DA:77,2576 +DA:77,2547 +DA:77,2343 +DA:77,2331 +BRDA:77,1,0,2279 BRDA:77,1,1,297 DA:78,297 DA:78,297 -DA:81,2275 -DA:81,2275 -BRDA:81,2,0,2268 +DA:81,2279 +DA:81,2279 +BRDA:81,2,0,2272 BRDA:81,2,1,7 DA:82,7 DA:82,7 -DA:85,2268 -DA:85,2268 -DA:85,2268 -DA:87,2268 -DA:87,2268 -DA:87,2268 -DA:88,2268 -DA:88,2268 -DA:88,2268 -DA:90,2268 -DA:90,2268 -DA:90,2268 -DA:96,2268 -DA:96,2268 -DA:96,2268 -DA:96,2268 +DA:85,2272 +DA:85,2272 +DA:85,2272 +DA:87,2272 +DA:87,2272 +DA:87,2272 +DA:88,2272 +DA:88,2272 +DA:88,2272 +DA:90,2272 +DA:90,2272 +DA:90,2272 +DA:96,2272 +DA:96,2272 +DA:96,2272 +DA:96,2272 FN:103,P256Verifier.ecAff_isValidPubkey -FNDA:2275,P256Verifier.ecAff_isValidPubkey -DA:107,2275 -DA:107,2275 -DA:107,2275 -DA:107,2275 -DA:107,2274 -BRDA:107,3,0,2273 +FNDA:2279,P256Verifier.ecAff_isValidPubkey +DA:107,2279 +DA:107,2279 +DA:107,2279 +DA:107,2279 +DA:107,2278 +BRDA:107,3,0,2277 BRDA:107,3,1,1 DA:108,2 DA:108,2 -DA:111,2273 -DA:111,2273 -DA:111,2273 +DA:111,2277 +DA:111,2277 +DA:111,2277 FN:114,P256Verifier.ecAff_satisfiesCurveEqn -FNDA:2273,P256Verifier.ecAff_satisfiesCurveEqn -DA:118,2273 -DA:118,2273 -DA:118,2273 -DA:119,2273 -DA:119,2273 -DA:119,2273 -DA:120,2273 -DA:120,2273 -DA:122,2273 -DA:122,2273 -DA:122,2273 +FNDA:2277,P256Verifier.ecAff_satisfiesCurveEqn +DA:118,2277 +DA:118,2277 +DA:118,2277 +DA:119,2277 +DA:119,2277 +DA:119,2277 +DA:120,2277 +DA:120,2277 +DA:122,2277 +DA:122,2277 +DA:122,2277 FN:130,P256Verifier.ecZZ_mulmuladd -FNDA:2268,P256Verifier.ecZZ_mulmuladd -DA:136,2268 -DA:136,2268 -DA:137,2268 -DA:137,2268 -DA:138,2268 -DA:138,2268 -DA:139,2268 -DA:139,2268 -DA:140,2268 -DA:140,2268 -DA:142,2268 -DA:142,2268 -DA:142,2268 +FNDA:2272,P256Verifier.ecZZ_mulmuladd +DA:136,2272 +DA:136,2272 +DA:137,2272 +DA:137,2272 +DA:138,2272 +DA:138,2272 +DA:139,2272 +DA:139,2272 +DA:140,2272 +DA:140,2272 +DA:142,2272 +DA:142,2272 +DA:142,2272 DA:142,0 -BRDA:142,4,0,2268 +BRDA:142,4,0,2272 BRDA:142,4,1,- DA:142,0 -DA:145,2268 -DA:145,2268 -DA:147,2268 -DA:147,2268 -DA:148,2268 -DA:148,2268 -DA:151,3080 -DA:151,3080 -DA:152,3080 -DA:152,3080 -DA:153,3080 -DA:153,3080 -DA:154,3080 -DA:154,3080 -BRDA:154,5,0,812 -BRDA:154,5,1,2268 +DA:145,2272 +DA:145,2272 +DA:147,2272 +DA:147,2272 +DA:148,2272 +DA:148,2272 +DA:151,3088 +DA:151,3088 +DA:152,3088 +DA:152,3088 +DA:153,3088 +DA:153,3088 +DA:154,3088 +DA:154,3088 +BRDA:154,5,0,816 +BRDA:154,5,1,2272 DA:154,0 -DA:160,2268 -DA:160,2268 +DA:160,2272 +DA:160,2272 BRDA:160,6,0,836 -BRDA:160,6,1,1432 +BRDA:160,6,1,1436 DA:161,836 DA:161,836 -DA:162,1432 -DA:162,1432 -BRDA:162,7,0,709 -BRDA:162,7,1,723 -DA:163,709 -DA:163,709 -DA:164,723 -DA:164,723 -BRDA:164,8,0,723 -BRDA:164,8,1,723 -DA:165,723 -DA:165,723 -DA:168,2268 -DA:168,2268 -DA:169,2268 -DA:169,2268 -DA:170,579796 -DA:170,579796 -DA:171,577528 -DA:171,577528 -DA:173,577528 -DA:173,577528 -DA:174,577528 -DA:174,577528 -DA:176,577528 -DA:176,577528 -BRDA:176,9,0,147784 -BRDA:176,9,1,429744 -DA:177,147784 -DA:177,147784 -DA:178,429744 -DA:178,429744 -BRDA:178,10,0,146865 -BRDA:178,10,1,282879 -DA:179,146865 -DA:179,146865 -DA:180,282879 -DA:180,282879 -BRDA:180,11,0,139945 -BRDA:180,11,1,142934 -DA:181,139945 -DA:181,139945 -DA:183,142934 -DA:183,142934 -DA:186,429744 -DA:186,429744 -DA:189,2268 -DA:189,2268 -DA:189,2268 -DA:190,2268 -DA:190,2268 +DA:162,1436 +DA:162,1436 +BRDA:162,7,0,711 +BRDA:162,7,1,725 +DA:163,711 +DA:163,711 +DA:164,725 +DA:164,725 +BRDA:164,8,0,725 +BRDA:164,8,1,725 +DA:165,725 +DA:165,725 +DA:168,2272 +DA:168,2272 +DA:169,2272 +DA:169,2272 +DA:170,580816 +DA:170,580816 +DA:171,578544 +DA:171,578544 +DA:173,578544 +DA:173,578544 +DA:174,578544 +DA:174,578544 +DA:176,578544 +DA:176,578544 +BRDA:176,9,0,148056 +BRDA:176,9,1,430488 +DA:177,148056 +DA:177,148056 +DA:178,430488 +DA:178,430488 +BRDA:178,10,0,147107 +BRDA:178,10,1,283381 +DA:179,147107 +DA:179,147107 +DA:180,283381 +DA:180,283381 +BRDA:180,11,0,140187 +BRDA:180,11,1,143194 +DA:181,140187 +DA:181,140187 +DA:183,143194 +DA:183,143194 +DA:186,430488 +DA:186,430488 +DA:189,2272 +DA:189,2272 +DA:189,2272 +DA:190,2272 +DA:190,2272 FN:203,P256Verifier.compute_bitpair -FNDA:580608,P256Verifier.compute_bitpair -DA:204,580608 -DA:204,580608 +FNDA:581632,P256Verifier.compute_bitpair +DA:204,581632 +DA:204,581632 FN:211,P256Verifier.ecAff_add -FNDA:2268,P256Verifier.ecAff_add -DA:220,2268 -DA:220,2268 -DA:221,2268 -DA:221,2268 -DA:223,2268 -DA:223,2268 -BRDA:223,12,0,2268 +FNDA:2272,P256Verifier.ecAff_add +DA:220,2272 +DA:220,2272 +DA:221,2272 +DA:221,2272 +DA:223,2272 +DA:223,2272 +BRDA:223,12,0,2272 BRDA:223,12,1,- DA:223,0 -DA:224,2268 -DA:224,2268 -BRDA:224,13,0,2268 +DA:224,2272 +DA:224,2272 +BRDA:224,13,0,2272 BRDA:224,13,1,- DA:224,0 -DA:226,2268 -DA:226,2268 -DA:228,2268 -DA:228,2268 -DA:228,2268 +DA:226,2272 +DA:226,2272 +DA:228,2272 +DA:228,2272 +DA:228,2272 FN:235,P256Verifier.ecAff_IsInf -FNDA:436568,P256Verifier.ecAff_IsInf -DA:241,436568 -DA:241,436568 +FNDA:437324,P256Verifier.ecAff_IsInf +DA:241,437324 +DA:241,437324 FN:248,P256Verifier.ecZZ_IsInf -FNDA:1011808,P256Verifier.ecZZ_IsInf -DA:255,1011808 -DA:255,1011808 +FNDA:1013576,P256Verifier.ecZZ_IsInf +DA:255,1013576 +DA:255,1013576 FN:265,P256Verifier.ecZZ_dadd_affine -FNDA:432012,P256Verifier.ecZZ_dadd_affine -DA:273,432012 -DA:273,432012 +FNDA:432760,P256Verifier.ecZZ_dadd_affine +DA:273,432760 +DA:273,432760 BRDA:273,14,0,272 BRDA:273,14,1,1020 DA:274,1292 @@ -269,39 +281,39 @@ DA:274,1020 DA:274,1020 DA:275,272 DA:275,272 -DA:276,430720 -DA:276,430720 -BRDA:276,16,0,430712 +DA:276,431468 +DA:276,431468 +BRDA:276,16,0,431460 BRDA:276,16,1,8 DA:277,8 DA:277,8 -DA:280,430712 -DA:280,430712 -DA:280,430712 -DA:281,430712 -DA:281,430712 -DA:281,430712 -DA:283,430712 -DA:283,430712 -BRDA:283,17,0,430636 +DA:280,431460 +DA:280,431460 +DA:280,431460 +DA:281,431460 +DA:281,431460 +DA:281,431460 +DA:283,431460 +DA:283,431460 +BRDA:283,17,0,431384 BRDA:283,17,1,- -DA:285,430636 -DA:285,430636 -DA:285,430636 -DA:286,430636 -DA:286,430636 -DA:286,430636 -DA:287,430636 -DA:287,430636 -DA:288,430636 -DA:288,430636 -DA:289,430636 -DA:289,430636 -DA:289,430636 -DA:290,430636 -DA:290,430636 -DA:295,430636 -DA:295,430636 +DA:285,431384 +DA:285,431384 +DA:285,431384 +DA:286,431384 +DA:286,431384 +DA:286,431384 +DA:287,431384 +DA:287,431384 +DA:288,431384 +DA:288,431384 +DA:289,431384 +DA:289,431384 +DA:289,431384 +DA:290,431384 +DA:290,431384 +DA:295,431384 +DA:295,431384 DA:300,76 DA:300,76 BRDA:300,18,0,20 @@ -310,39 +322,39 @@ DA:304,20 DA:304,20 DA:307,56 DA:307,56 -DA:310,430712 -DA:310,430712 +DA:310,431460 +DA:310,431460 FN:318,P256Verifier.ecZZ_double_zz -FNDA:577528,P256Verifier.ecZZ_double_zz -DA:320,577528 -DA:320,577528 -BRDA:320,19,0,575440 +FNDA:578544,P256Verifier.ecZZ_double_zz +DA:320,578544 +DA:320,578544 +BRDA:320,19,0,576456 BRDA:320,19,1,2088 DA:320,2088 DA:320,2088 -DA:322,575440 -DA:322,575440 -DA:322,575440 -DA:323,575440 -DA:323,575440 -DA:323,575440 -DA:324,575440 -DA:324,575440 -DA:324,575440 -DA:325,575440 -DA:325,575440 -DA:325,575440 -DA:326,575440 -DA:326,575440 -DA:326,575440 -DA:328,575440 -DA:328,575440 -DA:329,575440 -DA:329,575440 -DA:330,575440 -DA:330,575440 -DA:331,575440 -DA:331,575440 +DA:322,576456 +DA:322,576456 +DA:322,576456 +DA:323,576456 +DA:323,576456 +DA:323,576456 +DA:324,576456 +DA:324,576456 +DA:324,576456 +DA:325,576456 +DA:325,576456 +DA:325,576456 +DA:326,576456 +DA:326,576456 +DA:326,576456 +DA:328,576456 +DA:328,576456 +DA:329,576456 +DA:329,576456 +DA:330,576456 +DA:330,576456 +DA:331,576456 +DA:331,576456 FN:339,P256Verifier.ecZZ_double_affine FNDA:20,P256Verifier.ecZZ_double_affine DA:341,20 @@ -369,28 +381,28 @@ DA:349,20 DA:350,20 DA:350,20 FN:358,P256Verifier.ecZZ_SetAff -FNDA:2268,P256Verifier.ecZZ_SetAff -DA:364,2268 -DA:364,2268 -BRDA:364,21,0,2252 +FNDA:2272,P256Verifier.ecZZ_SetAff +DA:364,2272 +DA:364,2272 +BRDA:364,21,0,2256 BRDA:364,21,1,16 DA:365,16 DA:365,16 DA:366,16 DA:366,16 -DA:369,2252 -DA:369,2252 -DA:369,2252 -DA:370,2252 -DA:370,2252 -DA:370,2252 -DA:371,2252 -DA:371,2252 -DA:371,2252 -DA:376,2252 -DA:376,2252 -DA:377,2252 -DA:377,2252 +DA:369,2256 +DA:369,2256 +DA:369,2256 +DA:370,2256 +DA:370,2256 +DA:370,2256 +DA:371,2256 +DA:371,2256 +DA:371,2256 +DA:376,2256 +DA:376,2256 +DA:377,2256 +DA:377,2256 FN:383,P256Verifier.ecZZ_PointAtInf FNDA:3164,P256Verifier.ecZZ_PointAtInf DA:384,3164 @@ -400,25 +412,25 @@ FNDA:16,P256Verifier.ecAffine_PointAtInf DA:391,16 DA:391,16 FN:397,P256Verifier.nModInv -FNDA:2268,P256Verifier.nModInv -DA:398,2268 -DA:398,2268 -DA:398,2268 +FNDA:2272,P256Verifier.nModInv +DA:398,2272 +DA:398,2272 +DA:398,2272 FN:404,P256Verifier.pModInv -FNDA:4520,P256Verifier.pModInv -DA:405,4520 -DA:405,4520 -DA:405,4520 +FNDA:4528,P256Verifier.pModInv +DA:405,4528 +DA:405,4528 +DA:405,4528 FN:414,P256Verifier.modInv -FNDA:6788,P256Verifier.modInv -DA:421,6788 -DA:421,6788 -DA:422,6788 -DA:422,6788 +FNDA:6800,P256Verifier.modInv +DA:421,6800 +DA:421,6800 +DA:422,6800 +DA:422,6800 BRDA:422,22,0,- BRDA:422,22,1,- -DA:423,6788 -DA:423,6788 +DA:423,6800 +DA:423,6800 FNF:18 FNH:18 LF:121 @@ -588,3 +600,34 @@ LH:14 BRF:8 BRH:8 end_of_record +TN: +SF:test/P256.t.sol +FN:10,FakePrecompile. +FNDA:2,FakePrecompile. +DA:11,2 +DA:11,2 +DA:11,2 +DA:12,2 +DA:12,2 +BRDA:12,0,0,- +BRDA:12,0,1,- +DA:14,2 +DA:14,2 +DA:14,2 +DA:15,2 +DA:15,2 +BRDA:15,1,0,1 +BRDA:15,1,1,1 +DA:16,1 +DA:16,1 +DA:16,1 +DA:18,1 +DA:18,1 +DA:18,1 +FNF:1 +FNH:1 +LF:6 +LH:6 +BRF:4 +BRH:2 +end_of_record diff --git a/src/P256.sol b/src/P256.sol index 4eb05d7..c7792ea 100644 --- a/src/P256.sol +++ b/src/P256.sol @@ -3,8 +3,11 @@ pragma solidity 0.8.21; /** * Helper library for external contracts to verify P256 signatures. + * Tries to use RIP-7212 precompile if available on the chain, and if not falls + * back to more expensive Solidity implementation. **/ library P256 { + address constant PRECOMPILE = address(0x100); address constant VERIFIER = 0xc2b78104907F722DABAc4C69f826a522B2754De4; function verifySignatureAllowMalleability( @@ -15,10 +18,21 @@ library P256 { uint256 y ) internal view returns (bool) { bytes memory args = abi.encode(message_hash, r, s, x, y); - (bool success, bytes memory ret) = VERIFIER.staticcall(args); - assert(success); // never reverts, always returns 0 or 1 - return abi.decode(ret, (uint256)) == 1; + (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. + return abi.decode(ret, (uint256)) == 1; + } + + (bool fallbackSuccess, bytes memory fallbackRet) = VERIFIER.staticcall( + args + ); + assert(fallbackSuccess); // never reverts, always returns 0 or 1 + + return abi.decode(fallbackRet, (uint256)) == 1; } /// P256 curve order n/2 for malleability check diff --git a/test/P256.t.sol b/test/P256.t.sol index f64820b..5308d1a 100644 --- a/test/P256.t.sol +++ b/test/P256.t.sol @@ -6,6 +6,20 @@ import {stdJson} from "forge-std/StdJson.sol"; import {P256} from "../src/P256.sol"; import {P256Verifier} from "../src/P256Verifier.sol"; +contract FakePrecompile { + fallback(bytes calldata input) external returns (bytes memory) { + (bool success, bytes memory ret) = P256.VERIFIER.staticcall(input); + assert(success); + + uint256 retUint = abi.decode(ret, (uint256)); + if (retUint == 1) { + return abi.encode(1); + } else { + return abi.encode(); + } + } +} + contract P256Test is Test { uint256[2] public pubKey; @@ -57,4 +71,49 @@ contract P256Test is Test { res = P256.verifySignature(hash, r, s, pubKey[0], pubKey[1]); assertEq(res, true); } + + function testPrecompileUsage() public { + uint256 r = 0x01655c1753db6b61a9717e4ccc5d6c4bf7681623dd54c2d6babc55125756661c; + uint256 s = 7033802732221576339889804108463427183539365869906989872244893535944704590394; + + bytes32 hash = 0x267f9ea080b54bbea2443dff8aa543604564329783b6a515c6663a691c555490; + + uint gasBefore = gasleft(); + bool res = P256.verifySignatureAllowMalleability( + hash, + r, + s, + pubKey[0], + pubKey[1] + ); + assertEq(res, true); + uint gasUsedFallback = gasBefore - gasleft(); + assert(gasUsedFallback > 100_000); // no precompile, used Solidity implementation + + vm.etch(P256.PRECOMPILE, type(FakePrecompile).runtimeCode); + + gasBefore = gasleft(); + res = P256.verifySignatureAllowMalleability( + hash, + r, + s, + pubKey[0], + pubKey[1] + ); + assertEq(res, true); + uint gasUsedPrecompile = gasBefore - gasleft(); + assert(gasUsedPrecompile < gasUsedFallback); // precompile, used precompile + + gasBefore = gasleft(); + res = P256.verifySignatureAllowMalleability( + hash, + r + 1, + s, + pubKey[0], + pubKey[1] + ); + assertEq(res, false); + uint gasUsedInvalidSignature = gasBefore - gasleft(); + assert(gasUsedInvalidSignature > gasUsedFallback); // invalid signature, used precompile and failed, so fall back to Solidity implementation and failed with nearly double gas + } }