EIP-155

The EIP-155 “Simple Replay Attack Protection” standard specifies a replay-attack-protected transaction encoding, which includes a chain identifier inside the transaction data, prior to signing.

  • This ensures that transactions created for one blockchain are invalid on another blockchain. Therefore, transactions broadcast on one network cannot be replayed on another.
Chain Chain ID
Ethereum mainnet 1
Ropsten 3
Rinkeby 4

EIP-155 took effect after the Spurious Dragon hard fork (FORK_BLKNUM 2,675,000, CHAIN_ID 1).

  • If block.number >= FORK_BLKNUM and CHAIN_ID is available, then when computing the hash of a transaction for the purposes of signing, instead of hashing only six rlp encoded elements (nonce, gasprice, startgas, to, value, data), you SHOULD hash nine rlp encoded elements (nonce, gasprice, startgas, to, value, data, chainid, 0, 0). If you do, then the v of the signature MUST be set to $$\{0,1\} + CHAIN\_ID * 2 + 35$$ where $$\{0,1\}$$ is the parity of the $$y$$ value of the curve point for which r is the $$x$$-value in the secp256k1 signing process.
    • If you choose to only hash 6 values, then v continues to be set to $$\{0,1\} + 27$$ as previously.
  • If block.number >= FORK_BLKNUM and $$v = CHAIN\_ID * 2 + 35$$ or $$v = CHAIN\_ID * 2 + 36$$, then when computing the hash of a transaction for purposes of recovering, instead of hashing six rlp encoded elements (nonce, gasprice, startgas, to, value, data), hash nine rlp encoded elements (nonce, gasprice, startgas, to, value, data, chainid, 0, 0).
    • The currently existing signature scheme using $$v = 27$$ and $$v = 28$$ remains valid and continues to operate under the same rules as it did previously.

Public Key Recovery

Given an ECDSA signature $$(r, s)$$ and EC domain parameters, it is generally possible to determine the public key $$Q$$, at least to within a small number of choices.

  • This is useful for generating self-signed signatures; also useful in bandwidth constrained environments when transmission of public keys cannot be afforded.

Potentially, several candidate public keys can be recovered from a signature. At a small cost, the signer can generate the ECDSA signature in such a way that only one of the candidate public keys is viable, and such that the verifier has a very small additional cost of determining which is the correct public key.

  • 公钥是椭圆曲线上的点,椭圆曲线坐标 $$x, y \in [0, p-1]$$,而签名 $$r, s \in [1, n-1]$$,$$p > n$$,所以会存在多个点对应同一个 $$r$$ 的情况。

v of the signature makes the recovery process more efficient,.

ecrecover implementation:

// https://github.com/ethereum/go-ethereum/blob/v1.10.25/crypto/secp256k1/libsecp256k1/include/secp256k1_recovery.h#L28

/** Parse a compact ECDSA signature (64 bytes + recovery id).
 *
 *  Returns: 1 when the signature could be parsed, 0 otherwise
 *  Args: ctx:     a secp256k1 context object
 *  Out:  sig:     a pointer to a signature object
 *  In:   input64: a pointer to a 64-byte compact signature
 *        recid:   the recovery id (0, 1, 2 or 3)
 */
SECP256K1_API int secp256k1_ecdsa_recoverable_signature_parse_compact(
    const secp256k1_context* ctx,
    secp256k1_ecdsa_recoverable_signature* sig,
    const unsigned char *input64,
    int recid
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);

// https://github.com/ethereum/go-ethereum/blob/v1.10.25/crypto/secp256k1/libsecp256k1/src/ecdsa_impl.h#L273

static int secp256k1_ecdsa_sig_sign(const secp256k1_ecmult_gen_context *ctx, secp256k1_scalar *sigr, secp256k1_scalar *sigs, const secp256k1_scalar *seckey, const secp256k1_scalar *message, const secp256k1_scalar *nonce, int *recid) {
    // ...

    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);
    }
    secp256k1_scalar_mul(&n, sigr, seckey);
    secp256k1_scalar_add(&n, &n, message);
    secp256k1_scalar_inverse(sigs, nonce);
    secp256k1_scalar_mul(sigs, sigs, &n);
    secp256k1_scalar_clear(&n);
    secp256k1_gej_clear(&rp);
    secp256k1_ge_clear(&r);
    if (secp256k1_scalar_is_zero(sigs)) {
        return 0;
    }
    if (secp256k1_scalar_is_high(sigs)) {
        secp256k1_scalar_negate(sigs, sigs);
        if (recid) {
            *recid ^= 1;
        }
    }
    return 1;
}

// https://github.com/ethereum/go-ethereum/blob/v1.10.25/crypto/secp256k1/libsecp256k1/src/modules/recovery/main_impl.h#L170

Value of recid:

  • 0 - y is even, x is finite
  • 1 - y is odd, x is finite
  • 2 - y is even, x is too large
  • 3 - y is odd, x is too large

References