Skip to content

Commit 46d6ce9

Browse files
committed
crypto: allow importing compact secp256k1 pubkey through recovery
1 parent be08bad commit 46d6ce9

2 files changed

Lines changed: 34 additions & 0 deletions

File tree

crypto/secp256k1/key_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/base64"
55
"testing"
66

7+
"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
78
"github.com/stretchr/testify/require"
89

910
"github.com/MetaMask/go-did-it/crypto"
@@ -82,6 +83,24 @@ Uwyag4V8qWsP8e5ZSOOXDSYMplwbsAzsko9NYw4Jy9RHYHwFQ7dV
8283
})
8384
}
8485

86+
func TestPublicKeyFromCompactRecovery(t *testing.T) {
87+
pub, priv, err := GenerateKeyPair()
88+
require.NoError(t, err)
89+
90+
message := []byte("test message")
91+
hasher := crypto.SHA256.New()
92+
hasher.Write(message)
93+
hash := hasher.Sum(nil)
94+
95+
// SignCompact produces a 65-byte compact signature with the recovery flag prepended.
96+
compactSig := ecdsa.SignCompact(priv.Unwrap(), hash, true)
97+
require.Len(t, compactSig, 65)
98+
99+
recovered, err := PublicKeyFromCompactRecovery(hash, compactSig)
100+
require.NoError(t, err)
101+
require.True(t, pub.Equal(recovered))
102+
}
103+
85104
func TestSignatureASN1(t *testing.T) {
86105
// openssl ecparam -genkey -name secp256k1 -noout -out private.pem
87106
// openssl ec -in private.pem -pubout -out public.pem

crypto/secp256k1/public.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,21 @@ func PublicKeyFromXY(x, y []byte) (*PublicKey, error) {
4444
return &PublicKey{k: secp256k1.NewPublicKey(&xf, &yf)}, nil
4545
}
4646

47+
// PublicKeyFromCompactRecovery recovers the secp256k1 public key from a compact signature
48+
// and the hash of the signed message. The signature must be 65 bytes:
49+
// [recovery_flag (1 byte) | R (32 bytes) | S (32 bytes)].
50+
// Returns an error if recovery fails or the signature is malformed.
51+
func PublicKeyFromCompactRecovery(hash, signature []byte) (*PublicKey, error) {
52+
if len(signature) != 65 {
53+
return nil, fmt.Errorf("secp256k1: invalid compact signature length: expected 65 bytes, got %d", len(signature))
54+
}
55+
pub, _, err := ecdsa.RecoverCompact(signature, hash)
56+
if err != nil {
57+
return nil, fmt.Errorf("secp256k1: failed to recover public key: %w", err)
58+
}
59+
return &PublicKey{k: pub}, nil
60+
}
61+
4762
// PublicKeyFromPublicKeyMultibase decodes the public key from its Multibase form
4863
func PublicKeyFromPublicKeyMultibase(multibase string) (*PublicKey, error) {
4964
code, bytes, err := helpers.PublicKeyMultibaseDecode(multibase)

0 commit comments

Comments
 (0)