|
4 | 4 | "context" |
5 | 5 | "crypto/ecdsa" |
6 | 6 | "crypto/elliptic" |
7 | | - "crypto/x509" |
8 | 7 | "encoding/asn1" |
9 | 8 | "encoding/pem" |
10 | 9 | "fmt" |
@@ -106,23 +105,32 @@ func (s *KMSSigner) fetchAndCachePublicKey(ctx context.Context) error { |
106 | 105 | return fmt.Errorf("failed to get public key from KMS: %w", err) |
107 | 106 | } |
108 | 107 |
|
109 | | - // Parse the PEM-encoded public key |
110 | 108 | block, _ := pem.Decode([]byte(pubKeyResp.Pem)) |
111 | 109 | if block == nil { |
112 | 110 | return fmt.Errorf("failed to decode PEM block containing public key") |
113 | 111 | } |
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) |
116 | 123 | } |
117 | 124 |
|
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") |
122 | 129 | } |
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, |
126 | 134 | } |
127 | 135 |
|
128 | 136 | // Validate the curve |
@@ -235,71 +243,63 @@ func (s *KMSSigner) signAttempt(ctx context.Context, message []byte) ([]byte, er |
235 | 243 | // `hash` is the original message hash that was signed. |
236 | 244 | // `pubKey` is the public key corresponding to the KMS key used for signing. |
237 | 245 | func derToEthereumSignature(derSig, hash []byte, pubKey *ecdsa.PublicKey) ([]byte, error) { |
| 246 | + // DER signature structure |
238 | 247 | var sig struct { |
239 | 248 | R, S *big.Int |
240 | 249 | } |
| 250 | + |
| 251 | + // Decode DER signature |
241 | 252 | if _, err := asn1.Unmarshal(derSig, &sig); err != nil { |
242 | 253 | return nil, fmt.Errorf("failed to unmarshal DER signature: %w", err) |
243 | 254 | } |
244 | 255 |
|
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) |
251 | 258 |
|
| 259 | + // Fill R and S values |
252 | 260 | rBytes := sig.R.Bytes() |
253 | 261 | sBytes := sig.S.Bytes() |
254 | 262 |
|
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 |
259 | 264 | copy(signature[32-len(rBytes):32], rBytes) |
260 | 265 | copy(signature[64-len(sBytes):64], sBytes) |
261 | 266 |
|
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 |
284 | 272 | } |
285 | 273 |
|
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, |
299 | 299 | } |
300 | 300 |
|
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 |
303 | 303 | } |
304 | 304 |
|
305 | 305 | // SignerFn returns a `bind.SignerFn` compatible function for use with go-ethereum contract bindings. |
|
0 commit comments