Search code examples
c++crypto++elliptic-curvediffie-hellman

Using public key coordinate with Crypto++'s ECDH class


I'm using crypto++ for ECDH key agreement

ECDH.Agree(key, privateKey, outherpublicKey);

Given that for public key I have only X and Y coordinates. How to generate publicKey from this values ?

ECDH.Agree(key,privateKey, getPublicKey(X,Y))

Thanks in advance


Solution

  • Given that for public key I have only X and Y coordinates. How to generate publicKey from this values?
    ECDH.Agree(key,privateKey, getPublicKey(X,Y))

    {x,y} is a point on the curve, but its never been easy to work with it directly.

    This is all we really want to do but it does not work. The problem is ECDH::Domain are only domain parameters. The public point and the private exponent are layered on top.

    OID curve = ASN1::secp256r1();
    DL_GroupParameters_EC<ECP> params(curve);
    
    Integer x("...");
    Integer y("...");
    ECP::Point q(x, y);
    
    DL_PublicKey_EC<ECP> pubKey;
    pubKey.Initialize(params, q);
    
    ECDH < ECP >::Domain theirs(pubKey);
    

    To further complicate matters, the keys produced by the ECDH protocol are temporary or ephemeral. They are not meant to be persisted; rather, they are meant to be used once and discarded. So Crypto++ does not make it easy to persist them (by, say, providing a DEREncode).

    Analysis

    To use the {x,y} coordinate, we need to figure out how the library is using it. The ephemeral public and private keys are created in pubkey.h around line 1380. The code for them is below:

    void GeneratePrivateKey(RandomNumberGenerator &rng, byte *privateKey)
    {
        Integer x(rng, Integer::One(), GetAbstractGroupParameters().GetMaxExponent());
        x.Encode(privateKey, PrivateKeyLength());
    }
    
    void GeneratePublicKey(RandomNumberGenerator &rng, const byte *privateKey, byte *publicKey)
    {
        const DL_GroupParameters<T> &params = GetAbstractGroupParameters();
        Integer x(privateKey, PrivateKeyLength());
        Element y = params.ExponentiateBase(x);
        params.EncodeElement(true, y, publicKey);
    }
    

    The line of interest above is the params.EncodeElement(true, y, publicKey). To see what's going on there, we need to look at eccrypto.h around line 70, and note that reversible is true:

    void EncodeElement(bool reversible, const Element &element, byte *encoded)
    {
        if (reversible)
            GetCurve().EncodePoint(encoded, element, m_compress);
        else
            element.x.Encode(encoded, GetEncodedElementSize(false));
    }
    

    params.EncodeElement calls ECP::EncodePoint. To see what that does we can examine ecp.cpp around line 120. The routine writes a uncompressed point, but blocks x and y on the maximum size of the public element, which should be the field size or the subgroup order.

    void ECP::EncodePoint(BufferedTransformation &bt, const Point &P, bool compressed)
    {
        if (P.identity)
            NullStore().TransferTo(bt, EncodedPointSize(compressed));
        else if (compressed)
        {
            bt.Put(2 + P.y.GetBit(0));
            P.x.Encode(bt, GetField().MaxElementByteLength());
        }
        else
        {
            unsigned int len = GetField().MaxElementByteLength();
            bt.Put(4);      // uncompressed
            P.x.Encode(bt, len);
            P.y.Encode(bt, len);
        }
    }
    

    Don't worry too much about the BufferedTransformation. There are ways to turn a byte[] into one, and it happened before the code shown above. If you trace the code, you will see its transformed via an ArraySource:

    byte myArray[PublicEphemeralKeyLength()];
    ArraySource as(myArray, COUNTOF(myArray));
    

    Above, as is a BufferedTransformation that wraps the byte[] you passed into the function.

    The final open question is the maximum size of the field element. That appears to be the modulus size less one, in bytes:

    $ grep -I -A 1 MaxElementByteLength modarith.h 
        unsigned int MaxElementByteLength() const
            {return (m_modulus-1).ByteCount();}
    

    Agreement

    Given the above information, here's what you should do. You need to supply the values for x and y in ECP::Point q(x,y). They are just Crypto++ Integers.

    OID curve = ASN1::secp256r1();
    DL_GroupParameters_EC<ECP> params(curve);
    
    size_t size = params.GetEncodedElementSize(true);
    vector<byte> othersPublicKey(size);
    
    ECP::Point q(x,y);
    params.EncodeElement(true, q, &othersPublicKey[0]);
    

    Then you can call:

    ecdh.Agree(key, myPrivateKey, &othersPublicKey[0]);
    

    One note: params.GetEncodedElementSize(true) should equal PublicEphemeralKeyLength(). If they are not equal, then something is wrong.


    If you need to modify compression, then you can:

    params.SetPointCompression(true);
    

    I'll get this added to Crypto++'s Elliptic Curve Diffie-Hellman wiki page so others don't have to go rummaging for it.