Search code examples
goethereumbitcoinelliptic-curveecdsa

Unmarshalling EC Point of a specific curve in golang


I'm trying to parse an EC Point

04410478ed041c12ddaf693958f91f1174e0c790b2ff580ddca39bc2a4f78ad041dc379aaefe27cede2fa7601f90e3f397938ee53268564e346ac7a58aac8c28ca5415

to a ecdsa.PublickKey struct with the following code:

    x, y := readECPoint(elliptic.P256(), point)
    if x == nil || y == nil {
        panic("unable to parse the public key")
    }
    pubKey := &ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y}

    pubKeyBytes := elliptic.Marshal(curve, pubKey.X, pubKey.Y)

Browsing on github I noticed this piece of code, which made sense to me:

func readECPoint(curve elliptic.Curve, ecpoint []byte) (*big.Int, *big.Int) {
    x, y := elliptic.Unmarshal(curve, ecpoint)
    if x == nil {
        // http://docs.oasis-open.org/pkcs11/pkcs11-curr/v2.40/os/pkcs11-curr-v2.40-os.html#_ftn1
        // PKCS#11 v2.20 specified that the CKA_EC_POINT was to be store in a DER-encoded
        // OCTET STRING.
        var point asn1.RawValue
        asn1.Unmarshal(ecpoint, &point)
        if len(point.Bytes) > 0 {
            x, y = elliptic.Unmarshal(curve, point.Bytes)
        }
    }
    return x, y
}

Other things I tried were:

curve := new(secp256k1.BitCurve)
x, y := curve.Unmarshal(point)

However all those previous code always returns with x=nil and y=nil

I know the KeyPair was generated in an HSM with the bitcoin curve

asn1.ObjectIdentifier 1.3.132.0.10

Am I missing something else to properly parse an EC Point from a bitcoin/ethereum curve?


Solution

  • The first code snippet seems wrong, because elliptic.P256() returns a Curve which implements NIST P-256 also known as secp256r1 or prime256v1. But the bitcoin/etherium curve uses secp256k1 format, which is different.

    I suggest to use the go-etherium package to create the secp256k1 curve.

    I also found a problem with your key. It has a length of 67, but the size of the public key must be 65 (see the source code). As Topaco pointed out, the first two bytes are probably the ASN.1 encoding for an octet string (0x04) of length 65 bytes (0x41), so the actual key starts from the 3rd byte.

    I wrote this minimal example that parses your key using go-etherium:

    package main
    
    import (
        "encoding/hex"
        "fmt"
    
        "github.com/ethereum/go-ethereum/crypto/secp256k1"
    )
    
    func main() {
        c := *secp256k1.S256()
        data, err := hex.DecodeString("04410478ed041c12ddaf693958f91f1174e0c790b2ff580ddca39bc2a4f78ad041dc379aaefe27cede2fa7601f90e3f397938ee53268564e346ac7a58aac8c28ca5415")
        if err != nil {
            panic(err)
        }
        x, y := c.Unmarshal(data[2:])
        fmt.Printf("%v\n%v\n", x, y)
    }
    

    Output:

    54696312948195154784868816119600719747374302831648955727311433079671352319031
    69965364187890201668291488802478374883818025489090614849107393112609590498325