1

I've trying to understand how zkSNARK works in TornadoCash. I faced with the tricky thing in the code which I cannot understand. I researched the code of this smart contract at address: https://etherscan.io/address/0xce172ce1f20ec0b3728c9965470eaf994a03557a#code#L1

There is a pairing method which plays with memory and than do a static call:

    /* @return The result of computing the pairing check
     *         e(p1[0], p2[0]) *  .... * e(p1[n], p2[n]) == 1
     *         For example,
     *         pairing([P1(), P1().negate()], [P2(), P2()]) should return true.
     */
    function pairing(
        G1Point memory a1,
        G2Point memory a2,
        G1Point memory b1,
        G2Point memory b2,
        G1Point memory c1,
        G2Point memory c2,
        G1Point memory d1,
        G2Point memory d2
    ) internal view returns (bool) {
        G1Point[4] memory p1 = [a1, b1, c1, d1];
        G2Point[4] memory p2 = [a2, b2, c2, d2];
    uint256 inputSize = 24;
    uint256[] memory input = new uint256[](inputSize);

    for (uint256 i = 0; i < 4; i++) {
        uint256 j = i * 6;
        input[j + 0] = p1[i].X;
        input[j + 1] = p1[i].Y;
        input[j + 2] = p2[i].X[0];
        input[j + 3] = p2[i].X[1];
        input[j + 4] = p2[i].Y[0];
        input[j + 5] = p2[i].Y[1];
    }

    uint256[1] memory out;
    bool success;

    // solium-disable-next-line security/no-inline-assembly
    assembly {
        success := staticcall(sub(gas(), 2000), 8 /*??? why ???*/, add(input, 0x20), mul(inputSize, 0x20), out, 0x20)
        // Use "invalid" to make gas estimation work
        switch success case 0 { invalid() }
    }

    require(success, "pairing-opcode-failed");

    return out[0] != 0;
}

So, my question is why does this code do staticcall at address 8? What it is the magic constant?

eth
  • 85,679
  • 53
  • 285
  • 406
Valera Dubrava
  • 310
  • 2
  • 7
  • quacks like a precompile (built-in function) – user253751 Apr 14 '23 at 14:44
  • @Valera I helped with the precompile below but do not know the cryptography: feel free to post your own answer if you can explain more/better. All I can say is that you have to setup the inputs before calling the precompile. – eth Apr 15 '23 at 04:58

1 Answers1

2

Address 8 is a pairing "precompiled contract".

More specifically, EIP-197: Precompiled contracts for optimal ate pairing check on the elliptic curve alt_bn128.

For a cyclic group G (written additively) of prime order q let log_P: G -> F_q be the discrete logarithm on this group with respect to a generator P, i.e. log_P(x) is the smallest non-negative integer n such that n * P = x.

The precompiled contract is defined as follows, where the two groups G_1 and G_2 are defined by their generators P_1 and P_2 below. Both generators have the same prime order q.

Input: (a1, b1, a2, b2, ..., ak, bk) from (G_1 x G_2)^k
Output: If the length of the input is incorrect or any of the inputs are not elements of
        the respective group or are not encoded correctly, the call fails.
        Otherwise, return one if
        log_P1(a1) * log_P2(b1) + ... + log_P1(ak) * log_P2(bk) = 0
        (in F_q) and zero else.

Note that k is determined from the length of the input. Following the section on the encoding below, k is the length of the input divided by 192. If the input length is not a multiple of 192, the call fails. Empty input is valid and results in returning one.

In order to check that an input is an element of G_1, verifying the encoding of the coordinates and checking that they satisfy the curve equation (or is the encoding of infinity) is sufficient. For G_2, in addition to that, the order of the element has to be checked to be equal to the group order q = 21888242871839275222246405745257275088548364400416034343698204186575808495617.

eth
  • 85,679
  • 53
  • 285
  • 406