Skip to content

πŸ‘·β€β™‚οΈ WebAuthn support in all Pectra EVM ChainsΒ #1434

@0xkoiner

Description

@0xkoiner

Hello dear Vectorize ( @Vectorized ),
Im Alex, solidity dev at @openfort-xyz

Im happy to use with ur impl. of webAuthn. Unfortunately the lib unsupported in all chains who was upgraded to support 7702. The lib callin sha256 precompile and not continue to calll modexp in chains like: BNB, Base, Bera, GNO, Ink.

Please could add this functionality for the webAuthn lib 🀞?

Im attaching hereby tests ive done with ur lib and lib of // @author Coinbase (https://github.com/base-org/webauthn-sol)

// SDPX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {WebAuthn} from "lib/webauthn-sol/src/WebAuthn.sol";
import {Test, console2 as console} from "lib/forge-std/src/test.sol";
import {WebAuthn as WebAuthnVerifierSolady} from "lib/solady/src/utils/WebAuthn.sol";

contract WebAuthnChains is Test {
    // @author Coinbase (https://github.com/base-org/webauthn-sol)
    Verifier v;
    // @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/WebAuthn.sol)
    VerifierSolady vS;
    uint256 forkId;

    string public SEPOLIA_RPC;
    string public MAINET_RPC;
    string public BASE_RPC;
    string public BERA_RPC;
    string public BNB_RPC;
    string public GNO_RPC;
    string public INK_RPC;
    string public OP_RPC;


    bytes32 constant PUBLIC_KEY_X =
        hex"f014cc9fb4edba3c439a22423f580ad29cb177dbd5af224e4d068ef6374df083";
    bytes32 constant PUBLIC_KEY_Y =
        hex"f4c5322095ffa8db8344b7675f82eeadd2a17af4d9db9d4d4c582e8839ca391e";

    bytes32 public constant CHALLENGE =
        hex"cea3e080968320575bc01fbe2293a690683e321ac28cfb95a234e4b959e4fcfa";

    bytes32 public constant SIGNATURE_R =
        hex"cf1d727573eee8b3d301ab94aec432c1b7953969dcdf71388d14385630378a80";
    bytes32 public constant SIGNATURE_S =
        hex"417d9bafa3acecdbba348f851c1efc93c7ed780e2eddc7046767a1998db4f0c8";

    bytes public constant AUTHENTICATOR_DATA =
        hex"49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97631d00000000";

    string public constant CLIENT_DATA_JSON =
        "{\"type\":\"webauthn.get\",\"challenge\":\"zqPggJaDIFdbwB--IpOmkGg-MhrCjPuVojTkuVnk_Po\",\"origin\":\"http://localhost:5173\",\"crossOrigin\":false}";

    uint256 public constant CHALLENGE_INDEX = 23;
    uint256 public constant TYPE_INDEX = 1;

    function setUp() public {
        SEPOLIA_RPC = "https://eth-sepolia.g.alchemy.com/v2/EIOmdDtOw7ulufI5S27isOfZfW51PQXB";
        MAINET_RPC = "https://eth-mainnet.g.alchemy.com/v2/EIOmdDtOw7ulufI5S27isOfZfW51PQXB";
        BASE_RPC = "https://base-mainnet.g.alchemy.com/v2/EIOmdDtOw7ulufI5S27isOfZfW51PQXB";
        BERA_RPC = "https://berachain-mainnet.g.alchemy.com/v2/EIOmdDtOw7ulufI5S27isOfZfW51PQXB";
        BNB_RPC = "https://bnb-testnet.g.alchemy.com/v2/EIOmdDtOw7ulufI5S27isOfZfW51PQXB";
        GNO_RPC = "https://gnosis-mainnet.g.alchemy.com/v2/EIOmdDtOw7ulufI5S27isOfZfW51PQXB";
        INK_RPC = "https://ink-mainnet.g.alchemy.com/v2/EIOmdDtOw7ulufI5S27isOfZfW51PQXB";
        OP_RPC = "https://opt-mainnet.g.alchemy.com/v2/EIOmdDtOw7ulufI5S27isOfZfW51PQXB";
    }

    function test_Sepolia() public {
        forkId = vm.createFork(SEPOLIA_RPC);
        vm.selectFork(forkId);
        v = new Verifier();

        bool isValid = v.verify(
            CHALLENGE,
            true,
            AUTHENTICATOR_DATA,
            CLIENT_DATA_JSON,
            CHALLENGE_INDEX,
            TYPE_INDEX,
            SIGNATURE_R,
            SIGNATURE_S,
            PUBLIC_KEY_X,
            PUBLIC_KEY_Y
        );

        assertTrue(isValid);
    }

    function test_Mainet() public {
        forkId = vm.createFork(MAINET_RPC);
        vm.selectFork(forkId);
        v = new Verifier();

        bool isValid = v.verify(
            CHALLENGE,
            true,
            AUTHENTICATOR_DATA,
            CLIENT_DATA_JSON,
            CHALLENGE_INDEX,
            TYPE_INDEX,
            SIGNATURE_R,
            SIGNATURE_S,
            PUBLIC_KEY_X,
            PUBLIC_KEY_Y
        );

        assertTrue(isValid);
    }

    function test_Base() public {
        forkId = vm.createFork(BASE_RPC);
        vm.selectFork(forkId);
        v = new Verifier();

        bool isValid = v.verify(
            CHALLENGE,
            true,
            AUTHENTICATOR_DATA,
            CLIENT_DATA_JSON,
            CHALLENGE_INDEX,
            TYPE_INDEX,
            SIGNATURE_R,
            SIGNATURE_S,
            PUBLIC_KEY_X,
            PUBLIC_KEY_Y
        );

        assertTrue(isValid);
    }

    function test_Bera() public {
        forkId = vm.createFork(BERA_RPC);
        vm.selectFork(forkId);
        v = new Verifier();

        bool isValid = v.verify(
            CHALLENGE,
            true,
            AUTHENTICATOR_DATA,
            CLIENT_DATA_JSON,
            CHALLENGE_INDEX,
            TYPE_INDEX,
            SIGNATURE_R,
            SIGNATURE_S,
            PUBLIC_KEY_X,
            PUBLIC_KEY_Y
        );

        assertTrue(isValid);
    }

    function test_BNB() public {
        forkId = vm.createFork(BNB_RPC);
        vm.selectFork(forkId);
        v = new Verifier();

        bool isValid = v.verify(
            CHALLENGE,
            true,
            AUTHENTICATOR_DATA,
            CLIENT_DATA_JSON,
            CHALLENGE_INDEX,
            TYPE_INDEX,
            SIGNATURE_R,
            SIGNATURE_S,
            PUBLIC_KEY_X,
            PUBLIC_KEY_Y
        );

        assertTrue(isValid);
    }

    function test_GNO() public {
        forkId = vm.createFork(GNO_RPC);
        vm.selectFork(forkId);
        v = new Verifier();

        bool isValid =  v.verify(
            CHALLENGE,
            true,
            AUTHENTICATOR_DATA,
            CLIENT_DATA_JSON,
            CHALLENGE_INDEX,
            TYPE_INDEX,
            SIGNATURE_R,
            SIGNATURE_S,
            PUBLIC_KEY_X,
            PUBLIC_KEY_Y
        );

        assertTrue(isValid);
    }

    function test_Ink() public {
        forkId = vm.createFork(INK_RPC);
        vm.selectFork(forkId);
        v = new Verifier();

        bool isValid = v.verify(
            CHALLENGE,
            true,
            AUTHENTICATOR_DATA,
            CLIENT_DATA_JSON,
            CHALLENGE_INDEX,
            TYPE_INDEX,
            SIGNATURE_R,
            SIGNATURE_S,
            PUBLIC_KEY_X,
            PUBLIC_KEY_Y
        );

        assertTrue(isValid);
    }

    function test_OP() public {
        forkId = vm.createFork(OP_RPC);
        vm.selectFork(forkId);
        v = new Verifier();

        bool isValid = v.verify(
            CHALLENGE,
            true,
            AUTHENTICATOR_DATA,
            CLIENT_DATA_JSON,
            CHALLENGE_INDEX,
            TYPE_INDEX,
            SIGNATURE_R,
            SIGNATURE_S,
            PUBLIC_KEY_X,
            PUBLIC_KEY_Y
        );

        assertTrue(isValid);
    }

    function test_Sepolia_Solady() public {
        forkId = vm.createFork(SEPOLIA_RPC);
        vm.selectFork(forkId);
        vS = new VerifierSolady();

        bool isValid = vS.verifySoladySignature(
            CHALLENGE,
            true,
            AUTHENTICATOR_DATA,
            CLIENT_DATA_JSON,
            CHALLENGE_INDEX,
            TYPE_INDEX,
            SIGNATURE_R,
            SIGNATURE_S,
            PUBLIC_KEY_X,
            PUBLIC_KEY_Y
        );

        assertTrue(isValid);
    }

    function test_Mainet_Solady() public {
        forkId = vm.createFork(MAINET_RPC);
        vm.selectFork(forkId);
        vS = new VerifierSolady();

        bool isValid = vS.verifySoladySignature(
            CHALLENGE,
            true,
            AUTHENTICATOR_DATA,
            CLIENT_DATA_JSON,
            CHALLENGE_INDEX,
            TYPE_INDEX,
            SIGNATURE_R,
            SIGNATURE_S,
            PUBLIC_KEY_X,
            PUBLIC_KEY_Y
        );

        assertTrue(isValid);
    }

    function test_Base_Solady() public {
        forkId = vm.createFork(BASE_RPC);
        vm.selectFork(forkId);
        vS = new VerifierSolady();

        bool isValid = vS.verifySoladySignature(
            CHALLENGE,
            true,
            AUTHENTICATOR_DATA,
            CLIENT_DATA_JSON,
            CHALLENGE_INDEX,
            TYPE_INDEX,
            SIGNATURE_R,
            SIGNATURE_S,
            PUBLIC_KEY_X,
            PUBLIC_KEY_Y
        );

        assertTrue(isValid);
    }

    function test_Bera_Solady() public {
        forkId = vm.createFork(BERA_RPC);
        vm.selectFork(forkId);
        vS = new VerifierSolady();

        bool isValid = vS.verifySoladySignature(
            CHALLENGE,
            true,
            AUTHENTICATOR_DATA,
            CLIENT_DATA_JSON,
            CHALLENGE_INDEX,
            TYPE_INDEX,
            SIGNATURE_R,
            SIGNATURE_S,
            PUBLIC_KEY_X,
            PUBLIC_KEY_Y
        );

        assertTrue(isValid);
    }

    function test_BNB_Solady() public {
        forkId = vm.createFork(BNB_RPC);
        vm.selectFork(forkId);
        vS = new VerifierSolady();

        bool isValid = vS.verifySoladySignature(
            CHALLENGE,
            true,
            AUTHENTICATOR_DATA,
            CLIENT_DATA_JSON,
            CHALLENGE_INDEX,
            TYPE_INDEX,
            SIGNATURE_R,
            SIGNATURE_S,
            PUBLIC_KEY_X,
            PUBLIC_KEY_Y
        );

        assertTrue(isValid);
    }

    function test_GNO_Solady() public {
        forkId = vm.createFork(GNO_RPC);
        vm.selectFork(forkId);
        vS = new VerifierSolady();

        bool isValid = vS.verifySoladySignature(
            CHALLENGE,
            true,
            AUTHENTICATOR_DATA,
            CLIENT_DATA_JSON,
            CHALLENGE_INDEX,
            TYPE_INDEX,
            SIGNATURE_R,
            SIGNATURE_S,
            PUBLIC_KEY_X,
            PUBLIC_KEY_Y
        );

        assertTrue(isValid);
    }

    function test_Ink_Solady() public {
        forkId = vm.createFork(INK_RPC);
        vm.selectFork(forkId);
        vS = new VerifierSolady();

        bool isValid = vS.verifySoladySignature(
            CHALLENGE,
            true,
            AUTHENTICATOR_DATA,
            CLIENT_DATA_JSON,
            CHALLENGE_INDEX,
            TYPE_INDEX,
            SIGNATURE_R,
            SIGNATURE_S,
            PUBLIC_KEY_X,
            PUBLIC_KEY_Y
        );

        assertTrue(isValid);
    }

    function test_OP_Solady() public {
        forkId = vm.createFork(OP_RPC);
        vm.selectFork(forkId);
        vS = new VerifierSolady();

        bool isValid = vS.verifySoladySignature(
            CHALLENGE,
            true,
            AUTHENTICATOR_DATA,
            CLIENT_DATA_JSON,
            CHALLENGE_INDEX,
            TYPE_INDEX,
            SIGNATURE_R,
            SIGNATURE_S,
            PUBLIC_KEY_X,
            PUBLIC_KEY_Y
        );

        assertTrue(isValid);
    }
}

contract Verifier {
    function verify(
        bytes32 challenge,
        bool requireUserVerification,
        bytes memory authenticatorData,
        string memory clientDataJSON,
        uint256 challengeIndex,
        uint256 typeIndex,
        bytes32 r,
        bytes32 s,
        bytes32 x,
        bytes32 y
    ) public view returns (bool isValid) {
        uint256 rUint = uint256(r);
        uint256 sUint = uint256(s);
        uint256 xUint = uint256(x);
        uint256 yUint = uint256(y);

        // @audit-info ⚠️: Can be external
        WebAuthn.WebAuthnAuth memory auth = WebAuthn.WebAuthnAuth({
            authenticatorData: authenticatorData,
            clientDataJSON: clientDataJSON,
            challengeIndex: challengeIndex,
            typeIndex: typeIndex,
            r: rUint,
            s: sUint
        });

        // Todo: test of Converting good or not
        bytes memory challengeBytes = toBytes(challenge);
        isValid = WebAuthn.verify(challengeBytes, requireUserVerification, auth, xUint, yUint);

        return isValid;
    }

    function toBytes(bytes32 data) internal pure returns (bytes memory result) {
        result = new bytes(32);
        assembly {
            mstore(add(result, 32), data)
        }
    }
}

contract VerifierSolady {
    function verifySoladySignature(
        bytes32 challenge,
        bool requireUserVerification,
        bytes memory authenticatorData,
        string memory clientDataJSON,
        uint256 challengeIndex,
        uint256 typeIndex,
        bytes32 r,
        bytes32 s,
        bytes32 x,
        bytes32 y
    ) public view returns (bool isValid) {
        // @audit-info ⚠️: Can be external
        WebAuthnVerifierSolady.WebAuthnAuth memory auth = WebAuthnVerifierSolady.WebAuthnAuth({
            authenticatorData: authenticatorData,
            clientDataJSON: clientDataJSON,
            challengeIndex: challengeIndex,
            typeIndex: typeIndex,
            r: r,
            s: s
        });

        // Todo: test of Converting good or not
        bytes memory challengeBytes = toBytes(challenge);
        isValid = WebAuthnVerifierSolady.verify(challengeBytes, requireUserVerification, auth, x, y);

        return isValid;
    }

    function toBytes(bytes32 data) internal pure returns (bytes memory result) {
        result = new bytes(32);
        assembly {
            mstore(add(result, 32), data)
        }
    }
}

test of SEPOLIA_RPC, MAINET_RPC and OP_RPC passing as well since it call correct precompile:

    β”‚   β”‚   β”œβ”€ [1360] PRECOMPILES::modexp(32, 32, 32, 0x417d9bafa3acecdbba348f851c1efc93c7ed780e2eddc7046767a1998db4f0c8, 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254f, 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551) [staticcall]
    β”‚   β”‚   β”‚   └─ ← [Return] 0x0045b25f83a9d65d17382ec31df8a1ba3ce92ed69bb890396c4021dbf4705df4
    β”‚   β”‚   β”œβ”€ [1360] PRECOMPILES::modexp(32, 32, 32, 0x18cfcde3b8d7575f848fba319ecdfb33045a2d7a175dd9676da62431ae80a333, 0xffffffff00000001000000000000000000000000fffffffffffffffffffffffd, 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff) [staticcall]
    β”‚   β”‚   β”‚   └─ ← [Return] 0x89257cd9ec83eaf5fa85b2a3c4cf54d70ca4dd6675d7617905b10246a5e90f41
    β”‚   β”‚   └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000001

test of the rest chains:

    β”‚   β”œβ”€ [120] PRECOMPILES::sha256(0x7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a227a715067674a61444946646277422d2d49704f6d6b47672d4d6872436a5075566f6a546b75566e6b5f506f222c226f726967696e223a22687474703a2f2f6c6f63616c686f73743a35313733222c2263726f73734f726967696e223a66616c73657d) [staticcall]
    β”‚   β”‚   └─ ← [Return] 0x219e7c3fe7a11bb2c9a1df45b0edf5fc2643e7391b9e89e6725aa27a4e4f2308
    β”‚   β”œβ”€ [96] PRECOMPILES::sha256(0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97631d00000000219e7c3fe7a11bb2c9a1df45b0edf5fc2643e7391b9e89e6725aa27a4e4f2308) [staticcall]
    β”‚   β”‚   └─ ← [Return] 0xf8a2ad3bfb24311530aa0157be355d27d57f2f9e3b7f68bce248a729ab95aa75

hope you will have time for this.

Thanks and good day 🫰🫰🫰

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions