Can We Recover The Public Key from an ECDSA Signature?

With ECDSA we create a signature with a private key and then prove with a public key. A special feature of ECDSA is that we can recover the…

Photo by Signature Pro on Unsplash

Can We Recover The Public Key from an ECDSA Signature?

With ECDSA we create a signature with a private key and then prove with a public key. A special feature of ECDSA is that we can recover the public key from the signature.

With ECDSA we have a signature value of (r,s,v) and we can use these values to recover the public key. In the following, we see an Ethereum transaction, and where the r, s, and v values are stored with the transaction:

In this case, we will create a random private key, and then derive the public key. Next, we will generate an ECDSA signature for a given data value, and then recover the public key using two methods (SigToPub and Ecrecover) from github.com/ethereum/go-ethereum/crypto.

The following code is used to create a SHA-3 hash from the data, and then sign with the private key:

hash := crypto.Keccak256Hash(data)
fmt.Printf("Hash: %x\n", hash.Bytes())
fmt.Printf("\n=== Now using Ecrecover ===\n")
signature, _ := crypto.Sign(hash.Bytes(), privateKey)

The recovery of the key is then just implemented with:

sigPublicKey, _ := crypto.Ecrecover(hash.Bytes(), signature)

The code used is [here]:

package main
// Based on example here: https://stackoverflow.com/questions/51111605/how-do-i-recover-ecdsa-public-key-correctly-from-hashed-message-and-signature-in
import (
"bytes"
"crypto/rand"
"encoding/hex"
"fmt"
"os"
	"github.com/ethereum/go-ethereum/crypto"
)
func randomHex(n int) (string, error) {
bytes := make([]byte, n)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}
func main() {
	argCount := len(os.Args[1:])
	msg := "hello"
if argCount > 0 {
msg = os.Args[1]
}
	data := []byte(msg)
	getAddr, _ := randomHex(32)
	privateKey, _ := crypto.HexToECDSA(getAddr)
fmt.Printf("Private key: %s\n", getAddr)
fmt.Printf("Message to sign: %s\n", msg)
	publicKey := privateKey.PublicKey
	publicKeyBytes := crypto.FromECDSAPub(&publicKey)
	hash := crypto.Keccak256Hash(data)
fmt.Printf("Hash: %x\n", hash.Bytes())
	fmt.Printf("\n=== Now using Ecrecover ===\n")
signature, _ := crypto.Sign(hash.Bytes(), privateKey)
	fmt.Printf("ECDSA Signature: %x\n", signature)
fmt.Printf(" R: %x\n", signature[0:32]) // 32 bytes
fmt.Printf(" S: %x\n", signature[32:64]) // 32 bytes
fmt.Printf(" V: %x\n", signature[64:])
	sigPublicKey, _ := crypto.Ecrecover(hash.Bytes(), signature)
	fmt.Printf("\nOriginal public key: %x\n", publicKeyBytes)
fmt.Printf("Recovered public key: %x\n", sigPublicKey)
	rtn := bytes.Equal(sigPublicKey, publicKeyBytes)
	if rtn {
fmt.Printf("Public keys match\n\n")
}
	fmt.Printf("\n=== Now using FromECDSAPub ===\n")
sigPublicKeyECDSA, _ := crypto.SigToPub(hash.Bytes(), signature)
	sigPublicKeyBytes := crypto.FromECDSAPub(sigPublicKeyECDSA)
rtn = bytes.Equal(sigPublicKeyBytes, publicKeyBytes)
	fmt.Printf("Original public key: %x\n", publicKeyBytes)
fmt.Printf("Recovered public key: %x\n", sigPublicKeyBytes)
	if rtn {
fmt.Printf("Public keys match\n\n")
}
	signatureNoRecoverID := signature[:len(signature)-1]
verified := crypto.VerifySignature(publicKeyBytes, hash.Bytes(), signatureNoRecoverID)
fmt.Println(verified)
}

A sample run [here]:

NPrivate key: 00bb19aec0b23e3b0a221fe5c67cd7fe5ec05f882d7d79235b1a0640d3021a4f
Message to sign: hello123
Hash: dfd5f2c67870019364f62f07afa806ad52c0999d603f679ea6f7e38dac856291
=== Now using Ecrecover ===
ECDSA Signature: 86507e60b4cc0613bc27b5f1ec39cb2eb4b1f0b5859061196a6bfa15d440c0f00250acb9693c9621facd5436a07d07e7ed07f140008baf1cf5aec12d8e30048201
R: 86507e60b4cc0613bc27b5f1ec39cb2eb4b1f0b5859061196a6bfa15d440c0f0
S: 0250acb9693c9621facd5436a07d07e7ed07f140008baf1cf5aec12d8e300482
V: 01
Original public key: 04dfcfd60cd4d6a918398bf8174cf617097d004ac7c721b529ed9931f3239863a71f66d29e7b4de01b0ac3b0be3a0aa572da8b32cdc3ec9a81be94a5a7a3f62ba1
Recovered public key: 04dfcfd60cd4d6a918398bf8174cf617097d004ac7c721b529ed9931f3239863a71f66d29e7b4de01b0ac3b0be3a0aa572da8b32cdc3ec9a81be94a5a7a3f62ba1
Public keys match

=== Now using FromECDSAPub ===
Original public key: 04dfcfd60cd4d6a918398bf8174cf617097d004ac7c721b529ed9931f3239863a71f66d29e7b4de01b0ac3b0be3a0aa572da8b32cdc3ec9a81be94a5a7a3f62ba1
Recovered public key: 04dfcfd60cd4d6a918398bf8174cf617097d004ac7c721b529ed9931f3239863a71f66d29e7b4de01b0ac3b0be3a0aa572da8b32cdc3ec9a81be94a5a7a3f62ba1
Public keys match

The public key is a point on the elliptic curve, and in its uncompressed form, it begins with a 0x40, followed by 32 bytes of a x-co-ordinate, and 32 bytes for the y-co-ordinate. For example:

04dfcfd60cd4d6a918398bf8174cf617097d004ac7c721b529ed9931f3239863a71f66d29e7b4de01b0ac3b0be3a0aa572da8b32cdc3ec9a81be94a5a7a3f62ba1

Has a public key point of:

04
X=dfcfd60cd4d6a918398bf8174cf617097d004ac7c721b529ed9931f3239863a7
Y=1f66d29e7b4de01b0ac3b0be3a0aa572da8b32cdc3ec9a81be94a5a7a3f62ba1

Conclusions

Satoshi selected ECDSA for Bitcoin, and it has proven to be a good choice, as there have been very few issues with it.