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?

  • quacks like a precompile (built-in function)
    – user253751
    Apr 14, 2023 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, 2023 at 4:58

1 Answer 1


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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.