Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/FreshCryptoLib"]
path = lib/FreshCryptoLib
url = https://github.com/rdubois-crypto/FreshCryptoLib
[submodule "lib/solady"]
path = lib/solady
url = https://github.com/vectorized/solady
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/openzeppelin/openzeppelin-contracts
[submodule "lib/crypto-lib"]
path = lib/crypto-lib
url = https://github.com/get-smooth/crypto-lib
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

Webauthn-sol is a Solidity library for verifying WebAuthn authentication assertions. It builds on [Daimo's WebAuthn.sol](https://github.com/daimo-eth/p256-verifier/blob/master/src/WebAuthn.sol).

This library is optimized for Ethereum layer 2 rollup chains but will work on all EVM chains. Signature verification always attempts to use the [RIP-7212 precompile](https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md) and, if this fails, falls back to using [FreshCryptoLib](https://github.com/rdubois-crypto/FreshCryptoLib/blob/master/solidity/src/FCL_ecdsa.sol#L40).
This library is optimized for Ethereum layer 2 rollup chains but will work on all EVM chains. Signature verification always attempts to use the [RIP-7212 precompile](https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md) and, if this fails, falls back to using [SmoothCryptoLib](https://github.com/get-smooth/crypto-lib/blob/main/src/lib/libSCL_RIP7212.sol#L24).

> [!IMPORTANT]
> FreshCryptoLib uses the `ModExp` precompile (`address(0x05)`), which is not supported on some chains, such as [Polygon zkEVM](https://www.rollup.codes/polygon-zkevm#precompiled-contracts). This library will not work on such chains, unless they support the RIP-7212 precompile.
> SmoothCryptoLib provides universal EVM compatibility through pure Solidity implementation. Unlike the deprecated FCL, SCL doesn't require the `ModExp` precompile and works on all chains including Polygon zkEVM.

Code excerpts

Expand Down
1 change: 0 additions & 1 deletion lib/FreshCryptoLib
Submodule FreshCryptoLib deleted from 76f3f1
1 change: 1 addition & 0 deletions lib/crypto-lib
Submodule crypto-lib added at d714e9
47 changes: 31 additions & 16 deletions src/WebAuthn.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {FCL_ecdsa} from "FreshCryptoLib/FCL_ecdsa.sol";
import {FCL_Elliptic_ZZ} from "FreshCryptoLib/FCL_elliptic.sol";
import {SCL_RIP7212} from "crypto-lib/src/lib/libSCL_RIP7212.sol";
import {n} from "crypto-lib/src/fields/SCL_secp256r1.sol";
import {Base64} from "openzeppelin-contracts/contracts/utils/Base64.sol";
import {LibString} from "solady/utils/LibString.sol";

Expand All @@ -12,7 +12,7 @@ import {LibString} from "solady/utils/LibString.sol";
/// of Daimo.
///
/// @dev Attempts to use the RIP-7212 precompile for signature verification.
/// If precompile verification fails, it falls back to FreshCryptoLib.
/// If precompile verification fails, it falls back to SmoothCryptoLib.
///
/// @author Coinbase (https://github.com/base-org/webauthn-sol)
/// @author Daimo (https://github.com/daimo-eth/p256-verifier/blob/master/src/WebAuthn.sol)
Expand Down Expand Up @@ -45,7 +45,7 @@ library WebAuthn {
bytes1 private constant _AUTH_DATA_FLAGS_UV = 0x04;

/// @dev Secp256r1 curve order / 2 used as guard to prevent signature malleability issue.
uint256 private constant _P256_N_DIV_2 = FCL_Elliptic_ZZ.n / 2;
uint256 private constant _P256_N_DIV_2 = n / 2;

/// @dev The precompiled contract address to use for signature verification in the “secp256r1” elliptic curve.
/// See https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md.
Expand Down Expand Up @@ -148,17 +148,32 @@ library WebAuthn {
// 20. Using credentialPublicKey, verify that sig is a valid signature over the binary concatenation of authData
// and hash.
bytes32 messageHash = sha256(abi.encodePacked(webAuthnAuth.authenticatorData, clientDataJSONHash));
bytes memory args = abi.encode(messageHash, webAuthnAuth.r, webAuthnAuth.s, x, y);
// try the RIP-7212 precompile address
(bool success, bytes memory ret) = _VERIFIER.staticcall(args);
// staticcall will not revert if address has no code
// check return length
// note that even if precompile exists, ret.length is 0 when verification returns false
// so an invalid signature will be checked twice: once by the precompile and once by FCL.
// Ideally this signature failure is simulated offchain and no one actually pay this gas.
bool valid = ret.length > 0;
if (success && valid) return abi.decode(ret, (uint256)) == 1;

return FCL_ecdsa.ecdsa_verify(messageHash, webAuthnAuth.r, webAuthnAuth.s, x, y);

// Try the RIP-7212 precompile first for better gas efficiency
return _verifySignature(messageHash, webAuthnAuth.r, webAuthnAuth.s, x, y);
}

/// @dev Internal function to verify signature, reducing stack depth in main verify function
/// @param messageHash The hash of the message to verify
/// @param r The r value of the signature
/// @param s The s value of the signature
/// @param x The x coordinate of the public key
/// @param y The y coordinate of the public key
/// @return True if signature is valid, false otherwise
function _verifySignature(bytes32 messageHash, uint256 r, uint256 s, uint256 x, uint256 y)
private
view
returns (bool)
{
// Try the RIP-7212 precompile address
(bool success, bytes memory ret) = _VERIFIER.staticcall(abi.encode(messageHash, r, s, x, y));

// Check if precompile is available and returned valid result
if (success && ret.length > 0) {
return abi.decode(ret, (uint256)) == 1;
}

// Fallback to SCL pure Solidity implementation
return SCL_RIP7212.verify(messageHash, r, s, x, y);
}
}
10 changes: 5 additions & 5 deletions test/Utils.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {FCL_Elliptic_ZZ} from "FreshCryptoLib/FCL_elliptic.sol";
import {Base64Url} from "FreshCryptoLib/utils/Base64Url.sol";
import {n} from "crypto-lib/src/fields/SCL_secp256r1.sol";
import {Base64} from "openzeppelin-contracts/contracts/utils/Base64.sol";

struct WebAuthnInfo {
bytes authenticatorData;
Expand All @@ -11,10 +11,10 @@ struct WebAuthnInfo {
}

library Utils {
uint256 constant P256_N_DIV_2 = FCL_Elliptic_ZZ.n / 2;
uint256 constant P256_N_DIV_2 = n / 2;

function getWebAuthnStruct(bytes32 challenge) public pure returns (WebAuthnInfo memory) {
string memory challengeb64url = Base64Url.encode(abi.encode(challenge));
string memory challengeb64url = Base64.encodeURL(abi.encode(challenge));
string memory clientDataJSON = string(
abi.encodePacked(
'{"type":"webauthn.get","challenge":"', challengeb64url, '","origin":"https://sign.coinbase.com","crossOrigin":false}'
Expand All @@ -34,7 +34,7 @@ library Utils {
/// it will pass malleability checks.
function normalizeS(uint256 s) public pure returns (uint256) {
if (s > P256_N_DIV_2) {
return FCL_Elliptic_ZZ.n - s;
return n - s;
}

return s;
Expand Down
6 changes: 3 additions & 3 deletions test/WebAuthn.t.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Base64Url} from "FreshCryptoLib/utils/Base64Url.sol";
import {Base64} from "openzeppelin-contracts/contracts/utils/Base64.sol";
import {Test, console2} from "forge-std/Test.sol";

import {WebAuthn} from "../src/WebAuthn.sol";
Expand All @@ -15,7 +15,7 @@ contract WebAuthnTest is Test {
WebAuthn.WebAuthnAuth memory auth = WebAuthn.WebAuthnAuth({
authenticatorData: hex"49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000101",
clientDataJSON: string.concat(
'{"type":"webauthn.get","challenge":"', Base64Url.encode(challenge), '","origin":"http://localhost:3005"}'
'{"type":"webauthn.get","challenge":"', Base64.encodeURL(challenge), '","origin":"http://localhost:3005"}'
),
challengeIndex: 23,
typeIndex: 1,
Expand All @@ -31,7 +31,7 @@ contract WebAuthnTest is Test {
WebAuthn.WebAuthnAuth memory auth = WebAuthn.WebAuthnAuth({
authenticatorData: hex"49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763050000010a",
clientDataJSON: string.concat(
'{"type":"webauthn.get","challenge":"', Base64Url.encode(challenge), '","origin":"http://localhost:3005","crossOrigin":false}'
'{"type":"webauthn.get","challenge":"', Base64.encodeURL(challenge), '","origin":"http://localhost:3005","crossOrigin":false}'
),
challengeIndex: 23,
typeIndex: 1,
Expand Down
4 changes: 2 additions & 2 deletions test/Webauthn.fuzz.t.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {FCL_ecdsa} from "FreshCryptoLib/FCL_ecdsa.sol";
import {n} from "crypto-lib/src/fields/SCL_secp256r1.sol";
import {Test, Vm, console, stdJson} from "forge-std/Test.sol";

import {WebAuthn} from "../src/WebAuthn.sol";
Expand Down Expand Up @@ -34,7 +34,7 @@ contract WebAuthnFuzzTest is Test {

// Only interested in s > P256_N_DIV_2 cases.
if (webAuthnAuth.s <= Utils.P256_N_DIV_2) {
webAuthnAuth.s = FCL_ecdsa.n - webAuthnAuth.s;
webAuthnAuth.s = n - webAuthnAuth.s;
}

bool res = WebAuthn.verify({challenge: challenge, requireUV: uv, webAuthnAuth: webAuthnAuth, x: x, y: y});
Expand Down