Skip to content

Commit f8401ed

Browse files
committed
code review improvements
1 parent 58ebdeb commit f8401ed

6 files changed

Lines changed: 99 additions & 74 deletions

File tree

rollup/internal/config/relayer.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,9 @@ type AWSKMSSignerConfig struct {
146146
// KeyID is the KMS key id, alias or ARN of an asymmetric ECC_SECG_P256K1 / SIGN_VERIFY key.
147147
KeyID string `json:"key_id"`
148148
// Region is the AWS region of the key. Optional; falls back to the ambient AWS config
149-
// (AWS_REGION, shared config, instance/IRSA role) when empty.
150-
Region string `json:"region,omitempty"`
149+
// (AWS_REGION, shared config, instance/IRSA role) when empty, and startup fails if
150+
// no region resolves from either.
151+
Region string `json:"region"`
151152
// SignerAddress is the expected Ethereum address of the key. Required: it is validated
152153
// against the address derived from the KMS public key at startup so a misconfigured
153154
// key id fails fast instead of signing from an unexpected account.

rollup/internal/controller/relayer/l2_relayer.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,19 +1308,26 @@ func addrFromSignerConfig(config *config.SignerConfig) (common.Address, error) {
13081308
}
13091309
return crypto.PubkeyToAddress(privKey.PublicKey), nil
13101310
case sender.RemoteSignerType:
1311-
if config.RemoteSignerConfig.SignerAddress == "" {
1312-
return common.Address{}, fmt.Errorf("signer address is empty")
1311+
if config.RemoteSignerConfig == nil {
1312+
return common.Address{}, fmt.Errorf("remote_signer_config is missing")
13131313
}
1314-
return common.HexToAddress(config.RemoteSignerConfig.SignerAddress), nil
1314+
return parseSignerAddress(config.RemoteSignerConfig.SignerAddress)
13151315
case sender.AWSKMSSignerType:
1316-
if config.AWSKMSSignerConfig == nil || config.AWSKMSSignerConfig.SignerAddress == "" {
1317-
return common.Address{}, fmt.Errorf("aws kms signer address is empty")
1316+
if config.AWSKMSSignerConfig == nil {
1317+
return common.Address{}, fmt.Errorf("aws_kms_signer_config is missing")
13181318
}
1319-
if !common.IsHexAddress(config.AWSKMSSignerConfig.SignerAddress) {
1320-
return common.Address{}, fmt.Errorf("aws kms signer address %q is not a valid hex address", config.AWSKMSSignerConfig.SignerAddress)
1321-
}
1322-
return common.HexToAddress(config.AWSKMSSignerConfig.SignerAddress), nil
1319+
return parseSignerAddress(config.AWSKMSSignerConfig.SignerAddress)
13231320
default:
13241321
return common.Address{}, fmt.Errorf("failed to determine signer address, unknown signer type: %v", config.SignerType)
13251322
}
13261323
}
1324+
1325+
// parseSignerAddress parses a signer address configured for a signer that holds
1326+
// its key outside the process. An empty string is not a valid hex address, so
1327+
// this covers both a missing and a malformed value.
1328+
func parseSignerAddress(addr string) (common.Address, error) {
1329+
if !common.IsHexAddress(addr) {
1330+
return common.Address{}, fmt.Errorf("signer address %q is not a valid hex address", addr)
1331+
}
1332+
return common.HexToAddress(addr), nil
1333+
}

rollup/internal/controller/sender/README.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ so all transaction types — including blob transactions — are supported.
3333
- **`key_id`** — id, alias, or ARN of an asymmetric KMS key with key spec
3434
`ECC_SECG_P256K1` and key usage `SIGN_VERIFY`.
3535
- **`region`** — optional; falls back to the ambient AWS config
36-
(`AWS_REGION`, shared config, instance/IRSA role) when empty.
36+
(`AWS_REGION`, shared config, instance/IRSA role) when empty. Startup fails if
37+
no region resolves from either.
3738
- **`signer_address`****required**. The expected Ethereum address of the key.
3839
It is validated at startup against the address derived from the KMS public key,
3940
so a misconfigured `key_id` fails fast instead of signing from an unexpected
@@ -56,9 +57,16 @@ role, or environment) — never put credentials in the config file.
5657
There are two ways to get a key into KMS. Whichever you use, the signer never
5758
needs the raw private key — it derives the Ethereum address from the KMS public
5859
key (`keccak256(pubkey)[12:]`) at startup, and you put that address in
59-
`signer_address`. Get the address from `GetPublicKey` (the relayer logs it on
60-
startup, or derive it yourself from the returned point) and **fund it** before
61-
the sender goes live.
60+
`signer_address`. To get it, take the `PublicKey` returned by
61+
`aws kms get-public-key` (base64 SPKI DER), whose last 65 bytes are the
62+
uncompressed point `0x04 || X || Y`, and keccak256-hash the 64 bytes after the
63+
`0x04` prefix; the address is the last 20 bytes of that hash. Alternatively, start
64+
the relayer with a placeholder `signer_address`: startup fails with an error that
65+
names the address derived from the KMS key, which you then put in the config.
66+
67+
**Fund the address** before the sender goes live. Once configured correctly, the
68+
relayer logs `initialized AWS KMS signer` with the key id and address, so you can
69+
confirm which account a workload signs from.
6270

6371
**Option A — generate in KMS (recommended).** The private key is created inside
6472
the KMS HSM and is non-exportable: it provably never exists outside KMS.

rollup/internal/controller/sender/kms_signer.go

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import (
1212
kmstypes "github.com/aws/aws-sdk-go-v2/service/kms/types"
1313

1414
"github.com/scroll-tech/go-ethereum/common"
15+
gethTypes "github.com/scroll-tech/go-ethereum/core/types"
1516
"github.com/scroll-tech/go-ethereum/crypto"
17+
"github.com/scroll-tech/go-ethereum/log"
1618

1719
"scroll-tech/rollup/internal/config"
1820
)
@@ -24,24 +26,29 @@ type kmsAPI interface {
2426
Sign(ctx context.Context, params *kms.SignInput, optFns ...func(*kms.Options)) (*kms.SignOutput, error)
2527
}
2628

27-
// kmsSigner produces ECDSA secp256k1 signatures over transaction hashes using an
28-
// AWS KMS asymmetric key. The private key never leaves KMS: only the 32-byte
29-
// signing hash is sent, and KMS returns a DER-encoded signature which we turn
30-
// into Ethereum's 65-byte [R || S || V] form locally.
29+
// kmsSigner signs transactions with an AWS KMS asymmetric secp256k1 key. The
30+
// private key never leaves KMS: only the 32-byte signing hash is sent, and KMS
31+
// returns a DER-encoded signature which we turn into Ethereum's 65-byte
32+
// [R || S || V] form and apply to the transaction locally. Because the tx is
33+
// assembled locally, every tx type the sender builds is supported, including BlobTx.
3134
type kmsSigner struct {
32-
client kmsAPI
33-
keyID string
34-
addr common.Address
35+
client kmsAPI
36+
keyID string
37+
addr common.Address
38+
txSigner gethTypes.Signer
39+
}
40+
41+
// asn1AlgorithmIdentifier is the AlgorithmIdentifier of a SubjectPublicKeyInfo.
42+
type asn1AlgorithmIdentifier struct {
43+
Algorithm asn1.ObjectIdentifier
44+
Parameters asn1.ObjectIdentifier
3545
}
3646

3747
// asn1Spki mirrors the SubjectPublicKeyInfo DER structure returned by KMS
3848
// GetPublicKey for an ECC_SECG_P256K1 key. PublicKey holds the uncompressed
3949
// point (0x04 || X || Y).
4050
type asn1Spki struct {
41-
Algorithm struct {
42-
Algorithm asn1.ObjectIdentifier
43-
Parameters asn1.ObjectIdentifier
44-
}
51+
Algorithm asn1AlgorithmIdentifier
4552
PublicKey asn1.BitString
4653
}
4754

@@ -67,7 +74,7 @@ var (
6774
// newKMSSigner constructs a signer backed by a live AWS KMS key. cfg.SignerAddress
6875
// is required and validated against the address derived from the KMS public key,
6976
// so a wrong key id fails fast instead of signing from an unexpected account.
70-
func newKMSSigner(ctx context.Context, cfg *config.AWSKMSSignerConfig) (*kmsSigner, error) {
77+
func newKMSSigner(ctx context.Context, cfg *config.AWSKMSSignerConfig, chainID *big.Int) (*kmsSigner, error) {
7178
if cfg == nil {
7279
return nil, fmt.Errorf("aws_kms_signer_config is nil")
7380
}
@@ -89,13 +96,16 @@ func newKMSSigner(ctx context.Context, cfg *config.AWSKMSSignerConfig) (*kmsSign
8996
if err != nil {
9097
return nil, fmt.Errorf("aws kms signer: failed to load aws config: %w", err)
9198
}
99+
if awsCfg.Region == "" {
100+
return nil, fmt.Errorf("aws kms signer: aws region is not set (configure region or AWS_REGION)")
101+
}
92102

93-
return newKMSSignerWithClient(ctx, kms.NewFromConfig(awsCfg), cfg.KeyID, common.HexToAddress(cfg.SignerAddress))
103+
return newKMSSignerWithClient(ctx, kms.NewFromConfig(awsCfg), cfg.KeyID, common.HexToAddress(cfg.SignerAddress), chainID)
94104
}
95105

96106
// newKMSSignerWithClient is the testable core: it derives the key's address from
97107
// the KMS public key and asserts it matches expectedAddr.
98-
func newKMSSignerWithClient(ctx context.Context, client kmsAPI, keyID string, expectedAddr common.Address) (*kmsSigner, error) {
108+
func newKMSSignerWithClient(ctx context.Context, client kmsAPI, keyID string, expectedAddr common.Address, chainID *big.Int) (*kmsSigner, error) {
99109
pub, err := publicKeyFromKMS(ctx, client, keyID)
100110
if err != nil {
101111
return nil, err
@@ -104,7 +114,13 @@ func newKMSSignerWithClient(ctx context.Context, client kmsAPI, keyID string, ex
104114
if derivedAddr != expectedAddr {
105115
return nil, fmt.Errorf("aws kms signer: configured signer_address %s does not match address %s derived from KMS key %s", expectedAddr.Hex(), derivedAddr.Hex(), keyID)
106116
}
107-
return &kmsSigner{client: client, keyID: keyID, addr: derivedAddr}, nil
117+
log.Info("initialized AWS KMS signer", "keyID", keyID, "address", derivedAddr.Hex(), "chainID", chainID)
118+
return &kmsSigner{
119+
client: client,
120+
keyID: keyID,
121+
addr: derivedAddr,
122+
txSigner: gethTypes.LatestSignerForChainID(chainID),
123+
}, nil
108124
}
109125

110126
func publicKeyFromKMS(ctx context.Context, client kmsAPI, keyID string) (*ecdsa.PublicKey, error) {
@@ -140,6 +156,19 @@ func (k *kmsSigner) address() common.Address {
140156
return k.addr
141157
}
142158

159+
// signTx returns tx signed by the KMS key.
160+
func (k *kmsSigner) signTx(ctx context.Context, tx *gethTypes.Transaction) (*gethTypes.Transaction, error) {
161+
sig, err := k.sign(ctx, k.txSigner.Hash(tx).Bytes())
162+
if err != nil {
163+
return nil, err
164+
}
165+
signedTx, err := tx.WithSignature(k.txSigner, sig)
166+
if err != nil {
167+
return nil, fmt.Errorf("aws kms signer: failed to apply signature to tx: %w", err)
168+
}
169+
return signedTx, nil
170+
}
171+
143172
// sign returns the 65-byte [R || S || V] Ethereum signature over the given 32-byte hash.
144173
func (k *kmsSigner) sign(ctx context.Context, hash []byte) ([]byte, error) {
145174
out, err := k.client.Sign(ctx, &kms.SignInput{

rollup/internal/controller/sender/kms_signer_test.go

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,7 @@ type fakeKMS struct {
3434
func (f *fakeKMS) GetPublicKey(_ context.Context, _ *kms.GetPublicKeyInput, _ ...func(*kms.Options)) (*kms.GetPublicKeyOutput, error) {
3535
pubBytes := crypto.FromECDSAPub(&f.priv.PublicKey) // 0x04 || X || Y
3636
der, err := asn1.Marshal(asn1Spki{
37-
Algorithm: struct {
38-
Algorithm asn1.ObjectIdentifier
39-
Parameters asn1.ObjectIdentifier
40-
}{Algorithm: ecPublicKeyOID, Parameters: secp256k1OID},
37+
Algorithm: asn1AlgorithmIdentifier{Algorithm: ecPublicKeyOID, Parameters: secp256k1OID},
4138
PublicKey: asn1.BitString{Bytes: pubBytes, BitLength: len(pubBytes) * 8},
4239
})
4340
if err != nil {
@@ -66,10 +63,10 @@ func (f *fakeKMS) Sign(_ context.Context, in *kms.SignInput, _ ...func(*kms.Opti
6663
return &kms.SignOutput{Signature: der}, nil
6764
}
6865

69-
func newTestKMSSigner(t *testing.T, fake *fakeKMS) *kmsSigner {
66+
func newTestKMSSigner(t *testing.T, fake *fakeKMS, chainID *big.Int) *kmsSigner {
7067
t.Helper()
7168
expected := crypto.PubkeyToAddress(fake.priv.PublicKey)
72-
ks, err := newKMSSignerWithClient(context.Background(), fake, "test-key-id", expected)
69+
ks, err := newKMSSignerWithClient(context.Background(), fake, "test-key-id", expected, chainID)
7370
require.NoError(t, err)
7471
assert.Equal(t, expected, ks.address())
7572
return ks
@@ -81,10 +78,10 @@ func TestKMSSigner_AddressValidation(t *testing.T) {
8178
fake := &fakeKMS{priv: priv}
8279

8380
// matching address succeeds
84-
newTestKMSSigner(t, fake)
81+
newTestKMSSigner(t, fake, big.NewInt(534352))
8582

8683
// mismatching address fails fast
87-
_, err = newKMSSignerWithClient(context.Background(), fake, "test-key-id", common.HexToAddress("0xdeadbeef00000000000000000000000000000000"))
84+
_, err = newKMSSignerWithClient(context.Background(), fake, "test-key-id", common.HexToAddress("0xdeadbeef00000000000000000000000000000000"), big.NewInt(534352))
8885
require.Error(t, err)
8986
assert.Contains(t, err.Error(), "does not match")
9087
}
@@ -134,12 +131,11 @@ func TestKMSSigner_SignAllTxTypes(t *testing.T) {
134131

135132
for _, tc := range cases {
136133
t.Run(tc.name, func(t *testing.T) {
137-
ks := newTestKMSSigner(t, &fakeKMS{priv: priv, forceHi: tc.forceHi})
134+
ks := newTestKMSSigner(t, &fakeKMS{priv: priv, forceHi: tc.forceHi}, chainID)
138135
ts := &TransactionSigner{
139-
config: &config.SignerConfig{SignerType: AWSKMSSignerType},
140-
kmsSigner: ks,
141-
kmsTxSigner: gethTypes.LatestSignerForChainID(chainID),
142-
addr: ks.address(),
136+
config: &config.SignerConfig{SignerType: AWSKMSSignerType},
137+
kmsSigner: ks,
138+
addr: ks.address(),
143139
}
144140

145141
tx := gethTypes.NewTx(tc.txData)
@@ -183,10 +179,7 @@ func (f *rawKMS) Sign(_ context.Context, _ *kms.SignInput, _ ...func(*kms.Option
183179
func marshalSPKI(t *testing.T, algo, curve asn1.ObjectIdentifier, point []byte, bitLen int) []byte {
184180
t.Helper()
185181
der, err := asn1.Marshal(asn1Spki{
186-
Algorithm: struct {
187-
Algorithm asn1.ObjectIdentifier
188-
Parameters asn1.ObjectIdentifier
189-
}{Algorithm: algo, Parameters: curve},
182+
Algorithm: asn1AlgorithmIdentifier{Algorithm: algo, Parameters: curve},
190183
PublicKey: asn1.BitString{Bytes: point, BitLength: bitLen},
191184
})
192185
require.NoError(t, err)
@@ -207,7 +200,7 @@ func TestKMSSigner_MalformedPublicKey(t *testing.T) {
207200
evenPoint[len(evenPoint)-1] &^= 1
208201

209202
// control: a well-formed SPKI must still be accepted.
210-
_, err = newKMSSignerWithClient(context.Background(), &rawKMS{pub: marshalSPKI(t, ecPublicKeyOID, secp256k1OID, point, full)}, "k", addr)
203+
_, err = newKMSSignerWithClient(context.Background(), &rawKMS{pub: marshalSPKI(t, ecPublicKeyOID, secp256k1OID, point, full)}, "k", addr, big.NewInt(534352))
211204
require.NoError(t, err)
212205

213206
cases := []struct {
@@ -222,7 +215,7 @@ func TestKMSSigner_MalformedPublicKey(t *testing.T) {
222215
}
223216
for _, tc := range cases {
224217
t.Run(tc.name, func(t *testing.T) {
225-
_, err := newKMSSignerWithClient(context.Background(), &rawKMS{pub: tc.pub}, "k", addr)
218+
_, err := newKMSSignerWithClient(context.Background(), &rawKMS{pub: tc.pub}, "k", addr, big.NewInt(534352))
226219
require.Error(t, err)
227220
assert.Contains(t, err.Error(), tc.want)
228221
})
@@ -264,7 +257,7 @@ func TestKMSSigner_InvalidSignerAddress(t *testing.T) {
264257
_, err := newKMSSigner(context.Background(), &config.AWSKMSSignerConfig{
265258
KeyID: "some-key-id",
266259
SignerAddress: "not-a-hex-address",
267-
})
260+
}, big.NewInt(534352))
268261
require.Error(t, err)
269262
assert.Contains(t, err.Error(), "not a valid hex address")
270263
}

rollup/internal/controller/sender/transaction_signer.go

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,12 @@ const (
2929

3030
// TransactionSigner signs given transactions
3131
type TransactionSigner struct {
32-
config *config.SignerConfig
33-
auth *bind.TransactOpts
34-
rpcClient *rpc.Client
35-
kmsSigner *kmsSigner
36-
kmsTxSigner gethTypes.Signer
37-
nonce uint64
38-
addr common.Address
32+
config *config.SignerConfig
33+
auth *bind.TransactOpts
34+
rpcClient *rpc.Client
35+
kmsSigner *kmsSigner
36+
nonce uint64
37+
addr common.Address
3938
}
4039

4140
func NewTransactionSigner(ctx context.Context, config *config.SignerConfig, chainID *big.Int) (*TransactionSigner, error) {
@@ -68,15 +67,14 @@ func NewTransactionSigner(ctx context.Context, config *config.SignerConfig, chai
6867
addr: common.HexToAddress(config.RemoteSignerConfig.SignerAddress),
6968
}, nil
7069
case AWSKMSSignerType:
71-
ks, err := newKMSSigner(ctx, config.AWSKMSSignerConfig)
70+
ks, err := newKMSSigner(ctx, config.AWSKMSSignerConfig, chainID)
7271
if err != nil {
7372
return nil, fmt.Errorf("failed to create AWS KMS signer, err: %w", err)
7473
}
7574
return &TransactionSigner{
76-
config: config,
77-
kmsSigner: ks,
78-
kmsTxSigner: gethTypes.LatestSignerForChainID(chainID),
79-
addr: ks.address(),
75+
config: config,
76+
kmsSigner: ks,
77+
addr: ks.address(),
8078
}, nil
8179
default:
8280
return nil, fmt.Errorf("failed to create new transaction signer, unknown type: %v", config.SignerType)
@@ -109,18 +107,7 @@ func (ts *TransactionSigner) SignTransaction(ctx context.Context, tx *gethTypes.
109107
}
110108
return signedTx, nil
111109
case AWSKMSSignerType:
112-
// KMS signs the transaction hash, so we construct the signed tx locally.
113-
// This supports every tx type the sender builds, including BlobTx.
114-
sig, err := ts.kmsSigner.sign(ctx, ts.kmsTxSigner.Hash(tx).Bytes())
115-
if err != nil {
116-
log.Info("failed to sign tx with AWS KMS", "address", ts.addr.String(), "err", err)
117-
return nil, err
118-
}
119-
signedTx, err := tx.WithSignature(ts.kmsTxSigner, sig)
120-
if err != nil {
121-
return nil, fmt.Errorf("failed to apply KMS signature to tx, err: %w", err)
122-
}
123-
return signedTx, nil
110+
return ts.kmsSigner.signTx(ctx, tx)
124111
default:
125112
// this shouldn't happen, because SignerType is checked during creation
126113
return nil, fmt.Errorf("shouldn't happen, unknown signer type")

0 commit comments

Comments
 (0)