You can *kinda* abuse ECRECOVER to do ECMUL in secp256k1 today

It is important to note that this trick does not work for secp256k1’s full public key space as ecrecover fails if r >= N (N being the order of the curve).

Note that r is set to the public key’s x coordinate which is defined over the prime field and therefore in [1, P) with P >= N. However, the work to find a private key with a public key’s x coordinate greater than N is roughly 128 bit of work - and therefore of negligible probability.

PoC:

pragma solidity ^0.8.13;

import {Test} from "forge-std/Test.sol";

uint constant N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;
uint constant P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F;

function ecmul(uint x, uint y, uint scalar) pure returns(address) {
    return ecrecover(0, y % 2 != 0 ? 28 : 27, bytes32(x), bytes32(mulmod(scalar, x, N)));
}

function isOnCurve(uint x, uint y) pure returns (bool) {
    uint left = mulmod(y, y, P);
    uint right = addmod(mulmod(x, mulmod(x, x, P), P), 7, P);

    return left == right;
}

function toAddress(uint x, uint y) pure returns (address) {
    return address(uint160(uint(keccak256(abi.encode(x, y)))));
}

contract EcmulTest is Test {
    function test_ecmulVerify_PoC() public pure {
        // Note that [1]P = P.
        uint scalar = 1;

        uint x;
        uint y;

        // Make a valid secp256k1 point with x >= N.
        x = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeeffffc2e;
        y = 0xa0981151316a5be677beb8ab97d4a15eba3e4638bfdc86038afffe1f445f4496;
        require(isOnCurve(x, y));

        // Note that ecrecover fails.
        assertEq(ecmul(x, y, 1), address(0));

        // Now lets try some other point (generator).
        x = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798;
        y = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8;
        require(isOnCurve(x, y));

        // Note that ecrecover works as expected.
        assertEq(ecmul(x, y, scalar), toAddress(x, y));
    }
}