web3js – How to Recover Y-parity from DER-Signature for EIP-1559

eip-1559ethereumjs-txsignatureweb3js

I have a software system where signing ETH transactions happens in a black box. I give the information required, and it returns to me a DER-signature (30|totalLen|02|lenR|R|02|lenS|S).

Now I'm aware of the signature V value, which for EIP-155 transactions used to be the recId + (2 * chainId + 35). I determined this using web3.js and @ethereumjs/tx libraries with the following piece of code:

    const ethAddress = '0x123ba3f3.....3a4f1d8';
    ethjsTx.v = recId + 35 + 2 * chainId;
    const rawTx = ethjsTx.serialize().toString(16);
    const recoveredAddress = this.web3.accounts.recoverTransaction(rawTx);
    return recoveredAddress === ethAddress;

I just repeat this for 2 different recId values (0,1) until it returns true, and then I'll know the V-value.

For EIP-1559, the chainId is no longer relevant to the V value. V is simply the Y-parity of the signature. Now my question is how to find out this Y-value from a DER signature.

In this repository @line 290:

https://github.com/ethereum/go-ethereum/blob/master/crypto/secp256k1/libsecp256k1/src/ecdsa_impl.h

is the following code:

    if (recid) {
        /* The overflow condition is cryptographically unreachable as hitting it requires finding the discrete log
         * of some P where P.x >= order, and only 1 in about 2^127 points meet this criteria.
         */
        *recid = (overflow ? 2 : 0) | (secp256k1_fe_is_odd(&r.y) ? 1 : 0);
    }

They check whether r.y is odd, which is the value I need. Problem is, my R is a 32-byte hexadecimal, whereas in that code, r is an object with a separate y property. My questions are:

  1. How can I obtain this Y-value from my hexadecimal R string?
  2. Or could I also just refactor my old function like this:
    const ethAddress = '0x123ba3f3.....3a4f1d8';
    ethjsTx.v = 0 or 1;
    const rawTx = ethjsTx.serialize().toString(16);
    const recoveredAddress = this.web3.accounts.recoverTransaction(rawTx);
    return recoveredAddress === ethAddress;

and go with that instead? It just seems kinda inefficient.

Best Answer

I was looking for some way to directly acquire the Y-parity, instead of recovering it after the fact. Did some research after posting this question, particularly on this article:

I also asked a question on Cryptography Stack Exchange and got a definitive answer:

So apparently R and S are calculated using several different values. One of these values is the X-coordinate of a randomly chosen point on an Elliptic Curve. This randomly chosen point also has a Y-coordinate, but it is unused in the calculations. Still, this point is the one we need. But to extract it, you'd need to modify the signing code to return this y-parity point, which is not worth it in my case. Maybe if you want to micro-optimize you could pursue this course of action.

On top of that, Ethereum's Yellow Paper specifies the erecover() function used in Solidity to check whether a signature is valid for the given sending address. So to extract the address from the signed transaction is intended behaviour, therefore I think my original method, to try both 0 and 1 and check the public key, is good enough for me.