Skip to content

Commit 68876f3

Browse files
committed
fix lint errors and public key recovery
1 parent 6a69357 commit 68876f3

File tree

2 files changed

+60
-64
lines changed

2 files changed

+60
-64
lines changed

kms_signer.go

Lines changed: 58 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"crypto/ecdsa"
66
"crypto/elliptic"
7-
"crypto/x509"
87
"encoding/asn1"
98
"encoding/pem"
109
"fmt"
@@ -106,23 +105,32 @@ func (s *KMSSigner) fetchAndCachePublicKey(ctx context.Context) error {
106105
return fmt.Errorf("failed to get public key from KMS: %w", err)
107106
}
108107

109-
// Parse the PEM-encoded public key
110108
block, _ := pem.Decode([]byte(pubKeyResp.Pem))
111109
if block == nil {
112110
return fmt.Errorf("failed to decode PEM block containing public key")
113111
}
114-
if block.Type != "PUBLIC KEY" {
115-
return fmt.Errorf("invalid PEM block type: %s", block.Type)
112+
113+
// Parse the ASN.1 structure
114+
var spki struct {
115+
Algorithm struct {
116+
Algorithm asn1.ObjectIdentifier
117+
Parameters asn1.ObjectIdentifier
118+
}
119+
PublicKey asn1.BitString
120+
}
121+
if _, err := asn1.Unmarshal(block.Bytes, &spki); err != nil {
122+
return fmt.Errorf("failed to parse ASN.1 structure: %w", err)
116123
}
117124

118-
// Parse the public key using standard library functions
119-
genericPubKey, err := x509.ParsePKIXPublicKey(block.Bytes)
120-
if err != nil {
121-
return fmt.Errorf("failed to parse PKIX public key: %w", err)
125+
// Convert the public key bytes to ECDSA public key
126+
x, y := elliptic.Unmarshal(crypto.S256(), spki.PublicKey.Bytes)
127+
if x == nil {
128+
return fmt.Errorf("failed to unmarshal public key")
122129
}
123-
parsedPubKey, ok := genericPubKey.(*ecdsa.PublicKey)
124-
if !ok {
125-
return fmt.Errorf("key is not an ECDSA public key")
130+
parsedPubKey := &ecdsa.PublicKey{
131+
Curve: crypto.S256(),
132+
X: x,
133+
Y: y,
126134
}
127135

128136
// Validate the curve
@@ -235,71 +243,63 @@ func (s *KMSSigner) signAttempt(ctx context.Context, message []byte) ([]byte, er
235243
// `hash` is the original message hash that was signed.
236244
// `pubKey` is the public key corresponding to the KMS key used for signing.
237245
func derToEthereumSignature(derSig, hash []byte, pubKey *ecdsa.PublicKey) ([]byte, error) {
246+
// DER signature structure
238247
var sig struct {
239248
R, S *big.Int
240249
}
250+
251+
// Decode DER signature
241252
if _, err := asn1.Unmarshal(derSig, &sig); err != nil {
242253
return nil, fmt.Errorf("failed to unmarshal DER signature: %w", err)
243254
}
244255

245-
// Ensure S is in the lower half of the curve order (prevents malleability)
246-
// As per EIP-2: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2.md
247-
curveOrder := crypto.S256().Params().N
248-
if sig.S.Cmp(new(big.Int).Div(curveOrder, big.NewInt(2))) > 0 {
249-
sig.S.Sub(curveOrder, sig.S)
250-
}
256+
// Create the signature
257+
signature := make([]byte, 65)
251258

259+
// Fill R and S values
252260
rBytes := sig.R.Bytes()
253261
sBytes := sig.S.Bytes()
254262

255-
// Ethereum signature format [R (32 bytes) || S (32 bytes) || V (1 byte)]
256-
signature := make([]byte, 65)
257-
258-
// Left-pad R and S to 32 bytes
263+
// Ensure R and S are exactly 32 bytes
259264
copy(signature[32-len(rBytes):32], rBytes)
260265
copy(signature[64-len(sBytes):64], sBytes)
261266

262-
// Calculate recovery ID (V)
263-
// V = 27 + recovery_id (where recovery_id is 0 or 1)
264-
// Reference: go-ethereum/crypto/signature_nocgo.go's Sign function logic
265-
266-
// We need to try both possible recovery IDs (0 and 1) and see which one
267-
// recovers the correct public key.
268-
var recoveredPubKey *ecdsa.PublicKey
269-
270-
// Try V = 27 (recovery ID = 0)
271-
signature[64] = 0 // Use 0 for Ecrecover's V parameter
272-
recPubBytes, err := crypto.Ecrecover(hash, signature)
273-
// Need to handle errors from Ecrecover carefully
274-
if err == nil {
275-
// Unmarshal the recovered public key bytes (format: 0x04 || X || Y)
276-
x, y := elliptic.Unmarshal(crypto.S256(), recPubBytes)
277-
if x != nil { // Check if unmarshal was successful
278-
recoveredPubKey = &ecdsa.PublicKey{Curve: crypto.S256(), X: x, Y: y}
279-
if recoveredPubKey.X.Cmp(pubKey.X) == 0 && recoveredPubKey.Y.Cmp(pubKey.Y) == 0 {
280-
signature[64] = 27 // Set Ethereum V
281-
return signature, nil
282-
}
283-
}
267+
// Try v = 0
268+
signature[64] = 0x0
269+
if verifySignature(pubKey, hash, signature) {
270+
signature[64] = 0x1b // Ethereum signature requires 27 added to v
271+
return signature, nil
284272
}
285273

286-
// Try V = 28 (recovery ID = 1)
287-
signature[64] = 1 // Use 1 for Ecrecover's V parameter
288-
recPubBytes, err = crypto.Ecrecover(hash, signature)
289-
if err == nil {
290-
// Unmarshal the recovered public key bytes
291-
x, y := elliptic.Unmarshal(crypto.S256(), recPubBytes)
292-
if x != nil { // Check if unmarshal was successful
293-
recoveredPubKey = &ecdsa.PublicKey{Curve: crypto.S256(), X: x, Y: y}
294-
if recoveredPubKey.X.Cmp(pubKey.X) == 0 && recoveredPubKey.Y.Cmp(pubKey.Y) == 0 {
295-
signature[64] = 28 // Set Ethereum V
296-
return signature, nil
297-
}
298-
}
274+
// Try v = 1
275+
signature[64] = 0x1
276+
if verifySignature(pubKey, hash, signature) {
277+
signature[64] = 0x1c // Ethereum signature requires 27 added to v
278+
return signature, nil
279+
}
280+
281+
return nil, fmt.Errorf("failed to determine correct recovery ID")
282+
}
283+
284+
// verifySignature checks if a signature is valid for a given public key and hash
285+
func verifySignature(pubKey *ecdsa.PublicKey, hash, sig []byte) bool {
286+
// Try to recover the public key
287+
recoveredPub, err := crypto.Ecrecover(hash, sig)
288+
if err != nil {
289+
return false
290+
}
291+
292+
// Convert recovered public key using crypto/ecdh
293+
x, y := new(big.Int), new(big.Int)
294+
x.SetBytes(recoveredPub[1:33])
295+
y.SetBytes(recoveredPub[33:])
296+
recoveredKey := &ecdsa.PublicKey{
297+
X: x,
298+
Y: y,
299299
}
300300

301-
// If neither recovery ID worked, something is wrong.
302-
return nil, fmt.Errorf("failed to determine correct recovery ID (V) for signature")
301+
// Compare public keys
302+
return recoveredKey.X.Cmp(pubKey.X) == 0 && recoveredKey.Y.Cmp(pubKey.Y) == 0
303303
}
304304

305305
// SignerFn returns a `bind.SignerFn` compatible function for use with go-ethereum contract bindings.

kms_signer_integration_test.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@ func TestKMSSignerIntegration(t *testing.T) {
3030
config := getTestKeyConfig(t)
3131
ctx := context.Background()
3232

33-
signer, err := NewKMSSigner(ctx, KMSSignerConfig{
34-
KeyName: config.KeyName,
35-
})
33+
signer, err := NewKMSSigner(ctx, KMSSignerConfig(config))
3634
require.NoError(t, err)
3735
defer signer.Close()
3836

@@ -77,9 +75,7 @@ func TestKMSSignerIntegrationRetry(t *testing.T) {
7775
config := getTestKeyConfig(t)
7876
ctx := context.Background()
7977

80-
signer, err := NewKMSSigner(ctx, KMSSignerConfig{
81-
KeyName: config.KeyName,
82-
})
78+
signer, err := NewKMSSigner(ctx, KMSSignerConfig(config))
8379
require.NoError(t, err)
8480
defer signer.Close()
8581

0 commit comments

Comments
 (0)