Elliptic Curves Attacks

Elliptic Curves Attacks

A spot-the-bug challenge

In the context of smart contracts, elliptic curve cryptography is often used for secure key generation, digital signatures, and cryptographic operations. A spot the bug challenge was proposed to find a bug in a given contract and this article expands on that. [1]


contract EllipticCurve {
    struct Point {
        uint x;
        uint y;
    }
    uint private constant p = 0x0ded3dc4be6d0d1d91a46b371d;
    uint private constant a = 0x00000000000000000000000000;
    uint private constant b = 0x00000000000000000000000001;
    uint private constant n = 0xd0d3dc4be6d0d1d91a46b371e;
    Point private generator = Point(0x0b8f7b7b963b86f8a27ab0b288, 0x061bab2e6f5d749cbb10189162);

    function pointMultiply(Point memory point, uint scalar) public pure returns(Point memory) {
        require(isOnCurve(point), "point is not on the curve");

    Point memory result = Point(p, p); //consider point at infinity
    Point memory current = point;
    while (scalar != 0) {
        if (scalar & 1 == 1) {
            result = pointAddition(result, current);
        }
            current = pointAddition(current, current);
            scalar = scalar >> 1;
        }
         return result;
    }



    function verifySignature(bytes32 message, uint r, uint s, Point memory publicKey) public view returns(bool) {


            require(isOnCurve(publicKey), "Public key is not on the curve");
            require(r != 0 && s != 0 && r < n && s < n / 2 + 1, "Wrong signature");

            uint w = modInverse(s, n);
            uint u1 = mulmod(uint(sha256(abi.encodePacked(message))), w, n);
            uint u2 = mulmod(r, w, n);

            Point memory result = pointAddition(pointMultiply(generator, u1), pointMultiply(publicKey, u2));
                        return (result.x == r % n);

    }                    

    function isOnCurve(Point memory pt) public pure returns(bool) {
        if (pt.x >= p || pt.y >= p) {
            return false;
        }
        uint lhs = mulmod(pt.y, pt.y, p); 
        uint rhs = addmod(addmod(mulmod(pt.x, mulmod(pt.x, pt.x, p), p), mulmod(a, pt.x, p), p), b, p);
        return lhs == rhs;
    }


    function pointAddition(Point memory p1, Point memory p2) private pure returns(Point memory) {
        if (p1.x == p && p1.y == p) {
            return p2;
        }
        if (p2.x == p && p2.y == p) {
            return p1;
        }
        uint m;


        if (p1.x == p2.x) {
            if (p1.y == p2.y) {
                 m = mulmod(addmod(mulmod(3, mulmod(p1.x, p1.x, p), p), a, p), modInverse(mulmod(2,p1.y, p), p), p);
            } else {
                 return Point(p, p); // point at infinity
            }
            } else {
                m = mulmod(modDiff(p1.y, p2.y, p), modInverse(modDiff(p1.x, p2.x, p), p), p); 
    }

        uint x3 = modDiff(mulmod(m, m, p), addmod(p1.x, p2.x, p), p) % p;
        uint y3 = modDiff(mulmod(m, modDiff(p1.x, x3, p), p), p1.y, p) % p;


    return Point(x3, y3);
}

        function modInverse(uint aa, uint m) private pure returns(uint) {
            uint256 q = 0;
            uint256 newT = 1;
            uint256 r= m;
            uint256 t;
            while (aa != 0) {
                t = r / aa;
                (q, newT)=(newT, addmod(q, (m-mulmod(t, newT, m)), m));
                (r, aa)=(aa, r-aa * t);
            }
            return q;
        }

        function modDiff(uint x, uint y, uint mod) private pure returns(uint) {
            return x > y ? (x-y) : (mod - (y - x));
        }
}

Understanding the Contract

This is a basic implementation of elliptic curve operations over a finite field, particularly tailored for cryptographic operations like digital signature verification.

function pointMultiply(Point memory point, uint scalar) public pure returns(Point memory) {
        require(isOnCurve(point), "point is not on the curve");

    Point memory result = Point(p, p); //consider point at infinity
    Point memory current = point;
    while (scalar != 0) {
        if (scalar & 1 == 1) {
            result = pointAddition(result, current);
        }
            current = pointAddition(current, current);
            scalar = scalar >> 1;
        }
         return result;
    }

The pointMultiply() function takes two arguments: a point on the elliptic curve and a scalar, which is a positive integer. The function calculates the scalar multiple of the given point, which is another point on the curve.

function verifySignature(bytes32 message, uint r, uint s, Point memory publicKey) public view returns(bool) {


            require(isOnCurve(publicKey), "Public key is not on the curve");
            require(r != 0 && s != 0 && r < n && s < n / 2 + 1, "Wrong signature");

            uint w = modInverse(s, n);
            uint u1 = mulmod(uint(sha256(abi.encodePacked(message))), w, n);
            uint u2 = mulmod(r, w, n);

            Point memory result = pointAddition(pointMultiply(generator, u1), pointMultiply(publicKey, u2));
                        return (result.x == r % n);

    }

The verifySignature() function verifies a digital signature generated using the elliptic curve. It takes four arguments: the message to be signed, the signature components (r and s), and the public key of the signer. The function verifies whether the signature is valid by recomputing the signature based on the message and the public key and comparing it to the provided signature components.

 function isOnCurve(Point memory pt) public pure returns(bool) {
        if (pt.x >= p || pt.y >= p) {
            return false;
        }
        uint lhs = mulmod(pt.y, pt.y, p); 
        uint rhs = addmod(addmod(mulmod(pt.x, mulmod(pt.x, pt.x, p), p), mulmod(a, pt.x, p), p), b, p);
        return lhs == rhs;
    }

The isOnCurve() function determines whether the point satisfies the curve constraints. It ensures that only valid points are used in elliptic curve operations. Invalid points can lead to incorrect calculations and potentially compromise the security of cryptographic protocols.

  function pointAddition(Point memory p1, Point memory p2) private pure returns(Point memory) {
        if (p1.x == p && p1.y == p) {
            return p2;
        }
        if (p2.x == p && p2.y == p) {
            return p1;
        }
        uint m;


        if (p1.x == p2.x) {
            if (p1.y == p2.y) {
                 m = mulmod(addmod(mulmod(3, mulmod(p1.x, p1.x, p), p), a, p), modInverse(mulmod(2,p1.y, p), p), p);
            } else {
                 return Point(p, p); // point at infinity
            }
            } else {
                m = mulmod(modDiff(p1.y, p2.y, p), modInverse(modDiff(p1.x, p2.x, p), p), p); 
    }

        uint x3 = modDiff(mulmod(m, m, p), addmod(p1.x, p2.x, p), p) % p;
        uint y3 = modDiff(mulmod(m, modDiff(p1.x, x3, p), p), p1.y, p) % p;


    return Point(x3, y3);
}

The pointAddition() function takes two points on the curve as arguments and calculates their sum which is also a point on the curve.

The vulnerability

Elliptic curve over a finite field

The proposed vulnerability in the contract had something to do with a weakness in the elliptic curve. In Weierstrass form the general equation will be presented as

$$E(F p ​ ):y 2 ≡x 3 +ax+b(mod p)$$

  • a and b: Coefficients of the Weierstrass equation.

  • p: Prime number defining the finite field.

  • G: Base point (generator) that generates a cyclic subgroup.

  • n: Order of the subgroup generated by the base point.

  • h: Cofactor of the subgroup generated by the base point.

The specific values of a, b, p, G, n, and ℎ are chosen carefully to meet security requirements. Standardized curves, such as those recommended by organizations like NIST, are often used in practice. [1,2].

In the smart contract provided, the curve has these domain parameters;

    uint private constant p = 0x0ded3dc4be6d0d1d91a46b371d;
    uint private constant a = 0x00000000000000000000000000;
    uint private constant b = 0x00000000000000000000000001;
    uint private constant n = 0xd0d3dc4be6d0d1d91a46b371e;
    Point private generator = Point(0x0b8f7b7b963b86f8a27ab0b288, 0x061bab2e6f5d749cbb10189162);

Curves with such parameters don't exist in the standard databases and that can be an indicator that the smart contract uses a weak curve.

Attacks on Weak Curves, ECDLP

The Elliptic Curve Discrete Logarithm Problem (ECDLP) is at the core of ECC security. Given a base point Q on an elliptic curve E(Fp​), finding the integer n such that P=nQ is believed to be computationally hard, forming the basis for the security of many cryptographic protocols. Weak curves, those with smaller field sizes, composite-order groups, or less secure structures, make the ECDLP easier to solve, potentially compromising the security of an ECC system.

Weak curves can be susceptible to various attacks on the Elliptic Curve Discrete Logarithm Problem (ECDLP). Here are a few types of attacks that are commonly employed against weak elliptic curves:

  • Pohlig-Hellman Attack

  • Index-Calculus Attack

  • Fault Attacks

  • Special Curve Structures

  • Pairing based: MOV attack, Frey-Rück attack

The combination of the Pohlig-Hellman algorithm and Pollard's rho algorithm is a widely recognized general-purpose attack on the elliptic curve discrete logarithm problem (ECDLP). This attack exploits the substructure of the elliptic curve's order using the Pohlig-Hellman algorithm and then applies Pollard's rho algorithm to efficiently solve the ECDLP for each prime factor of the order.

The Pohlig-Hellman algorithm divides the ECDLP into smaller subproblems based on the prime factorization of the elliptic curve's order. This reduces the computational complexity of the problem by allowing each subproblem to be solved independently.

Pollard's rho algorithm, on the other hand, is a probabilistic algorithm that can efficiently find discrete logarithms in various groups, including elliptic curve groups. It is particularly effective for groups with smooth orders, where the prime factors are well-distributed. Combining these two algorithms results in a powerful attack that can significantly reduce the difficulty of solving the ECDLP for certain elliptic curves.

Subexponential-time attacks are those that can solve the DLP in a time complexity that grows less than exponentially with the size of the input. This means that as the size of the input increases, the time required to solve the DLP grows more slowly than if it were an exponential-time attack.

While exponential-time attacks are generally considered infeasible for practical use, subexponential-time attacks can pose a significant threat to ECC systems if they are not carefully designed and implemented. Curves that are vulnerable to these attacks can be broken more efficiently, compromising the security of the system.

Check the curve's domain parameters

Checking the parameters of our contract's elliptic curve using the SageMath Script, the attacker can see that the ECDLP in this curve is solvable via either the Pohlig-Hellman algorithm or a MOV attack.

The attack vector

Attack Description:

  1. Transaction Collection:

    • The attacker collects a set of transactions where the verifySignature function is called. This implies that public keys and signatures associated with these transactions are publicly visible on the blockchain.
  2. Public Key Extraction:

    • The attacker extracts public keys from the transactions' calldata. Public keys are often included in transaction data to facilitate signature verification.
  3. Private Key Calculation:

    • The attacker attempts to calculate the sender's private key using either the Pohlig-Hellman algorithm or a MOV (Menezes, Okamoto, Vanstone) attack. These attacks exploit weaknesses in the elliptic curve structure, potentially leading to the extraction of private keys.
  4. Impersonation:

    • Once the attacker has obtained the private keys, they can impersonate the original sender by generating valid signatures for arbitrary messages. This enables them to create transactions that appear valid and originate from the compromised account.

References:

  1. https://gist.github.com/pqlx/d0bdf2d0c4a2aa400b2b52d9bd9b7b65

  2. https://wstein.org/edu/2010/414/projects/novotney.pdf

  3. http://koclab.cs.ucsb.edu/teaching/ecc/project/2015Projects/Sommerseth+Hoeiland.pdf

  4. https://hexens.io/blog/spot-the-bug-challenge-3

  5. https://cryptobook.nakov.com/asymmetric-key-ciphers/elliptic-curve-cryptography-ecc

  6. https://eprint.iacr.org/2018/307.pdf