|
1 | 1 | package signer |
2 | 2 |
|
3 | 3 | import ( |
4 | | - "context" |
5 | | - "crypto/ecdsa" |
6 | | - "crypto/elliptic" |
7 | | - "crypto/x509" |
8 | 4 | "encoding/asn1" |
9 | | - "encoding/hex" |
10 | | - "encoding/pem" |
11 | | - "fmt" |
12 | 5 | "math/big" |
13 | 6 | "testing" |
14 | 7 |
|
15 | | - kmspb "cloud.google.com/go/kms/apiv1/kmspb" |
16 | | - "github.com/ethereum/go-ethereum/common" |
17 | | - "github.com/ethereum/go-ethereum/core/types" |
18 | 8 | "github.com/ethereum/go-ethereum/crypto" |
19 | | - "github.com/stretchr/testify/assert" |
20 | 9 | "github.com/stretchr/testify/require" |
21 | 10 | ) |
22 | 11 |
|
23 | | -type mockKMSClient struct { |
24 | | - privateKey *ecdsa.PrivateKey |
25 | | -} |
26 | | - |
27 | | -func newMockKMSSigner(t *testing.T) *KMSSigner { |
28 | | - privateKeyHex := "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" |
29 | | - privateKeyBytes, err := hex.DecodeString(privateKeyHex) |
30 | | - require.NoError(t, err, "Failed to decode private key hex") |
31 | | - |
32 | | - privateKey, err := crypto.ToECDSA(privateKeyBytes) |
33 | | - require.NoError(t, err, "Failed to create private key") |
34 | | - |
35 | | - mock := &mockKMSClient{ |
36 | | - privateKey: privateKey, |
37 | | - } |
38 | | - |
39 | | - signer := &KMSSigner{ |
40 | | - client: mock, |
41 | | - keyName: "test-key", |
42 | | - } |
43 | | - |
44 | | - err = signer.fetchAndCachePublicKey(context.Background()) |
45 | | - require.NoError(t, err, "Failed to fetch and cache public key") |
46 | | - |
47 | | - return signer |
48 | | -} |
49 | | - |
50 | | -func (m *mockKMSClient) GetPublicKey(ctx context.Context, req *kmspb.GetPublicKeyRequest) (*kmspb.PublicKey, error) { |
51 | | - |
52 | | - x509EncodedPub, err := x509.MarshalPKIXPublicKey(&m.privateKey.PublicKey) |
53 | | - if err != nil { |
54 | | - return nil, fmt.Errorf("failed to marshal public key to X.509: %w", err) |
55 | | - } |
56 | | - |
57 | | - pemBytes := pem.EncodeToMemory(&pem.Block{ |
58 | | - Type: "PUBLIC KEY", |
59 | | - Bytes: x509EncodedPub, |
60 | | - }) |
61 | | - |
62 | | - return &kmspb.PublicKey{ |
63 | | - Pem: string(pemBytes), |
64 | | - Algorithm: kmspb.CryptoKeyVersion_EC_SIGN_SECP256K1_SHA256, |
65 | | - }, nil |
66 | | -} |
67 | | - |
68 | | -func (m *mockKMSClient) AsymmetricSign(ctx context.Context, req *kmspb.AsymmetricSignRequest) (*kmspb.AsymmetricSignResponse, error) { |
69 | | - hash := req.GetDigest().GetSha256() |
70 | | - |
71 | | - if len(hash) != 32 { |
72 | | - return nil, fmt.Errorf("hash is required to be exactly 32 bytes (%d)", len(hash)) |
73 | | - } |
74 | | - |
75 | | - signature, err := crypto.Sign(hash, m.privateKey) |
76 | | - if err != nil { |
77 | | - return nil, fmt.Errorf("failed to sign hash: %w", err) |
78 | | - } |
79 | | - |
80 | | - r := new(big.Int).SetBytes(signature[:32]) |
81 | | - s := new(big.Int).SetBytes(signature[32:64]) |
82 | | - |
83 | | - derSignature, err := asn1.Marshal(struct { |
84 | | - R, S *big.Int |
85 | | - }{r, s}) |
86 | | - if err != nil { |
87 | | - return nil, fmt.Errorf("failed to marshal DER signature: %w", err) |
88 | | - } |
89 | | - |
90 | | - return &kmspb.AsymmetricSignResponse{ |
91 | | - Signature: derSignature, |
92 | | - }, nil |
93 | | -} |
94 | | - |
95 | | -func (m *mockKMSClient) Close() error { |
96 | | - return nil |
97 | | -} |
98 | | - |
99 | | -func TestKMSSignerWithMock(t *testing.T) { |
100 | | - signer := newMockKMSSigner(t) |
101 | | - |
102 | | - tests := []struct { |
103 | | - name string |
104 | | - message []byte |
105 | | - }{ |
106 | | - { |
107 | | - name: "Simple message", |
108 | | - message: []byte("Hello, world!"), |
109 | | - }, |
110 | | - { |
111 | | - name: "Empty message", |
112 | | - message: []byte{}, |
113 | | - }, |
114 | | - { |
115 | | - name: "Long message", |
116 | | - message: []byte("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."), |
117 | | - }, |
118 | | - } |
119 | | - |
120 | | - for _, tt := range tests { |
121 | | - t.Run(tt.name, func(t *testing.T) { |
122 | | - hash := crypto.Keccak256(tt.message) |
123 | | - signature, err := signer.Sign(context.Background(), hash) |
124 | | - require.NoError(t, err) |
125 | | - |
126 | | - assert.Equal(t, 65, len(signature)) |
127 | | - |
128 | | - if signature[64] != 0x1b && signature[64] != 0x1c { |
129 | | - t.Fatalf("Invalid recovery ID: %x", signature[64]) |
130 | | - } |
131 | | - |
132 | | - signature[64] -= 27 |
133 | | - |
134 | | - recoveredPub, err := crypto.Ecrecover(hash, signature) |
135 | | - require.NoError(t, err) |
136 | | - |
137 | | - x, y := elliptic.Unmarshal(crypto.S256(), recoveredPub) |
138 | | - recoveredKey := &ecdsa.PublicKey{ |
139 | | - Curve: crypto.S256(), |
140 | | - X: x, |
141 | | - Y: y, |
142 | | - } |
143 | | - |
144 | | - assert.NotNil(t, recoveredKey.X) |
145 | | - assert.NotNil(t, recoveredKey.Y) |
146 | | - }) |
147 | | - } |
148 | | -} |
149 | | - |
150 | 12 | func TestDERToEthereumSignature(t *testing.T) { |
151 | 13 | privateKey, err := crypto.GenerateKey() |
152 | 14 | require.NoError(t, err, "Failed to generate test key pair") |
@@ -182,61 +44,3 @@ func TestDERToEthereumSignature(t *testing.T) { |
182 | 44 | require.Equal(t, ethSig[:32], convertedSig[:32], "R value mismatch") |
183 | 45 | require.Equal(t, ethSig[32:64], convertedSig[32:64], "S value mismatch") |
184 | 46 | } |
185 | | - |
186 | | -func TestEIP155TransactionSigning(t *testing.T) { |
187 | | - privateKeyHex := "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" |
188 | | - privateKeyBytes, err := hex.DecodeString(privateKeyHex) |
189 | | - require.NoError(t, err, "Failed to decode private key hex") |
190 | | - |
191 | | - privateKey, err := crypto.ToECDSA(privateKeyBytes) |
192 | | - require.NoError(t, err, "Failed to create private key") |
193 | | - |
194 | | - address := crypto.PubkeyToAddress(privateKey.PublicKey) |
195 | | - |
196 | | - chainIDs := []*big.Int{ |
197 | | - big.NewInt(1), // Ethereum Mainnet |
198 | | - } |
199 | | - |
200 | | - for _, chainID := range chainIDs { |
201 | | - t.Run(fmt.Sprintf("ChainID_%d", chainID), func(t *testing.T) { |
202 | | - tx := types.NewTransaction( |
203 | | - 0, // nonce |
204 | | - common.Address{}, // to |
205 | | - big.NewInt(0), // amount |
206 | | - uint64(21000), // gasLimit |
207 | | - big.NewInt(1000000000), // gasPrice |
208 | | - []byte{}, // data |
209 | | - ) |
210 | | - |
211 | | - ethSigner := types.NewEIP155Signer(chainID) |
212 | | - signedTx, err := types.SignTx(tx, ethSigner, privateKey) |
213 | | - require.NoError(t, err) |
214 | | - |
215 | | - sender, err := types.Sender(ethSigner, signedTx) |
216 | | - require.NoError(t, err) |
217 | | - |
218 | | - require.Equal(t, address.Hex(), sender.Hex()) |
219 | | - |
220 | | - t.Log("Testing with KMS signer") |
221 | | - signer := newMockKMSSigner(t) |
222 | | - |
223 | | - signerFn := signer.SignerFn(chainID) |
224 | | - |
225 | | - tx2 := types.NewTransaction( |
226 | | - 0, // nonce |
227 | | - common.Address{}, // to |
228 | | - big.NewInt(0), // amount |
229 | | - uint64(21000), // gasLimit |
230 | | - big.NewInt(1000000000), // gasPrice |
231 | | - []byte{}, // data |
232 | | - ) |
233 | | - |
234 | | - signedTx2, err := signerFn(signer.Address(), tx2) |
235 | | - require.NoError(t, err) |
236 | | - |
237 | | - sender2, err := types.Sender(ethSigner, signedTx2) |
238 | | - require.NoError(t, err) |
239 | | - require.Equal(t, signer.Address().Hex(), sender2.Hex()) |
240 | | - }) |
241 | | - } |
242 | | -} |
0 commit comments