From 7b7cda516b3c2666cd0d8d274af30dd36b0aaea7 Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Mon, 16 Mar 2026 18:23:58 +0100 Subject: [PATCH 1/3] Start ksm --- go.mod | 15 ++++ go.sum | 30 +++++++ pkg/cmd/init.go | 37 ++++++-- pkg/cmd/run_node.go | 67 ++++++++------ pkg/config/config.go | 12 ++- pkg/config/config_test.go | 4 +- pkg/config/defaults.go | 2 + pkg/signer/aws/signer.go | 163 ++++++++++++++++++++++++++++++++++ pkg/signer/aws/signer_test.go | 156 ++++++++++++++++++++++++++++++++ 9 files changed, 450 insertions(+), 36 deletions(-) create mode 100644 pkg/signer/aws/signer.go create mode 100644 pkg/signer/aws/signer_test.go diff --git a/go.mod b/go.mod index 6c35bfe87b..bc25b70e03 100644 --- a/go.mod +++ b/go.mod @@ -45,6 +45,21 @@ require ( require ( github.com/armon/go-metrics v0.4.1 // indirect + github.com/aws/aws-sdk-go-v2 v1.41.4 // indirect + github.com/aws/aws-sdk-go-v2/config v1.32.12 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 // indirect + github.com/aws/aws-sdk-go-v2/service/kms v1.50.3 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect + github.com/aws/smithy-go v1.24.2 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/boltdb/bolt v1.3.1 // indirect diff --git a/go.sum b/go.sum index a9b47945c0..433d75d0f4 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,36 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/aws/aws-sdk-go-v2 v1.41.4 h1:10f50G7WyU02T56ox1wWXq+zTX9I1zxG46HYuG1hH/k= +github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= +github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0= +github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g= +github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8= +github.com/aws/aws-sdk-go-v2/credentials v1.19.12/go.mod h1:U3R1RtSHx6NB0DvEQFGyf/0sbrpJrluENHdPy1j/3TE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 h1:zOgq3uezl5nznfoK3ODuqbhVg1JzAGDUhXOsU0IDCAo= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20/go.mod h1:z/MVwUARehy6GAg/yQ1GO2IMl0k++cu1ohP9zo887wE= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 h1:CNXO7mvgThFGqOFgbNAP2nol2qAWBOGfqR/7tQlvLmc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20/go.mod h1:oydPDJKcfMhgfcgBUZaG+toBbwy8yPWubJXBVERtI4o= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 h1:tN6W/hg+pkM+tf9XDkWUbDEjGLb+raoBMFsTodcoYKw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20/go.mod h1:YJ898MhD067hSHA6xYCx5ts/jEd8BSOLtQDL3iZsvbc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 h1:2HvVAIq+YqgGotK6EkMf+KIEqTISmTYh5zLpYyeTo1Y= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20/go.mod h1:V4X406Y666khGa8ghKmphma/7C0DAtEQYhkq9z4vpbk= +github.com/aws/aws-sdk-go-v2/service/kms v1.50.3 h1:s/zDSG/a/Su9aX+v0Ld9cimUCdkr5FWPmBV8owaEbZY= +github.com/aws/aws-sdk-go-v2/service/kms v1.50.3/go.mod h1:/iSgiUor15ZuxFGQSTf3lA2FmKxFsQoc2tADOarQBSw= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 h1:0GFOLzEbOyZABS3PhYfBIx2rNBACYcKty+XGkTgw1ow= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.8/go.mod h1:LXypKvk85AROkKhOG6/YEcHFPoX+prKTowKnVdcaIxE= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 h1:kiIDLZ005EcKomYYITtfsjn7dtOwHDOFy7IbPXKek2o= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.13/go.mod h1:2h/xGEowcW/g38g06g3KpRWDlT+OTfxxI0o1KqayAB8= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 h1:jzKAXIlhZhJbnYwHbvUQZEB8KfgAEuG0dc08Bkda7NU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17/go.mod h1:Al9fFsXjv4KfbzQHGe6V4NZSZQXecFcvaIF4e70FoRA= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 h1:Cng+OOwCHmFljXIxpEVXAGMnBia8MSU6Ch5i9PgBkcU= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.9/go.mod h1:LrlIndBDdjA/EeXeyNBle+gyCwTlizzW5ycgWnvIxkk= +github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= +github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= diff --git a/pkg/cmd/init.go b/pkg/cmd/init.go index f6c9a075f4..79201a63b2 100644 --- a/pkg/cmd/init.go +++ b/pkg/cmd/init.go @@ -1,6 +1,7 @@ package cmd import ( + "context" "fmt" "os" "path/filepath" @@ -8,12 +9,18 @@ import ( rollconf "github.com/evstack/ev-node/pkg/config" "github.com/evstack/ev-node/pkg/hash" "github.com/evstack/ev-node/pkg/p2p/key" + awssigner "github.com/evstack/ev-node/pkg/signer/aws" "github.com/evstack/ev-node/pkg/signer/file" ) // CreateSigner sets up the signer configuration and creates necessary files func CreateSigner(config *rollconf.Config, homePath string, passphrase string) ([]byte, error) { - if config.Signer.SignerType == "file" && config.Node.Aggregator { + if !config.Node.Aggregator { + return nil, nil + } + + switch config.Signer.SignerType { + case "file": if passphrase == "" { return nil, fmt.Errorf("passphrase is required when using local file signer") } @@ -42,11 +49,31 @@ func CreateSigner(config *rollconf.Config, homePath string, passphrase string) ( proposerAddress := hash.SumTruncated(bz) return proposerAddress, nil - } else if config.Signer.SignerType != "file" && config.Node.Aggregator { - return nil, fmt.Errorf("remote signer not implemented for aggregator nodes, use local signer instead") - } - return nil, nil + case "awskms": + // For KMS, the key is pre-provisioned in AWS. We fetch the public key + // to derive the proposer address. + signer, err := awssigner.NewKmsSigner(context.Background(), config.Signer.KmsRegion, config.Signer.KmsKeyID) + if err != nil { + return nil, fmt.Errorf("failed to initialize AWS KMS signer: %w", err) + } + + pubKey, err := signer.GetPublic() + if err != nil { + return nil, fmt.Errorf("failed to get public key from KMS: %w", err) + } + + bz, err := pubKey.Raw() + if err != nil { + return nil, fmt.Errorf("failed to get public key raw bytes: %w", err) + } + + proposerAddress := hash.SumTruncated(bz) + return proposerAddress, nil + + default: + return nil, fmt.Errorf("unknown signer type: %s", config.Signer.SignerType) + } } // LoadOrGenNodeKey creates the node key file if it doesn't exist. diff --git a/pkg/cmd/run_node.go b/pkg/cmd/run_node.go index 4a5c7577c6..52ffbdb546 100644 --- a/pkg/cmd/run_node.go +++ b/pkg/cmd/run_node.go @@ -26,6 +26,7 @@ import ( "github.com/evstack/ev-node/pkg/p2p" "github.com/evstack/ev-node/pkg/p2p/key" "github.com/evstack/ev-node/pkg/signer" + awssigner "github.com/evstack/ev-node/pkg/signer/aws" "github.com/evstack/ev-node/pkg/signer/file" "github.com/evstack/ev-node/pkg/telemetry" ) @@ -110,40 +111,50 @@ func StartNode( // Validate and load signer first (before attempting DA connection, which may fail // eagerly over WebSocket if no DA server is running). var signer signer.Signer - if nodeConfig.Signer.SignerType == "file" && (nodeConfig.Node.Aggregator && !nodeConfig.Node.BasedSequencer) { - // Get passphrase file path - passphraseFile, err := cmd.Flags().GetString(rollconf.FlagSignerPassphraseFile) - if err != nil { - return fmt.Errorf("failed to get '%s' flag: %w", rollconf.FlagSignerPassphraseFile, err) - } + if nodeConfig.Node.Aggregator && !nodeConfig.Node.BasedSequencer { + switch nodeConfig.Signer.SignerType { + case "file": + // Get passphrase file path + passphraseFile, err := cmd.Flags().GetString(rollconf.FlagSignerPassphraseFile) + if err != nil { + return fmt.Errorf("failed to get '%s' flag: %w", rollconf.FlagSignerPassphraseFile, err) + } - if passphraseFile == "" { - return fmt.Errorf("passphrase file must be provided via --evnode.signer.passphrase_file") - } + if passphraseFile == "" { + return fmt.Errorf("passphrase file must be provided via --evnode.signer.passphrase_file") + } - // Read passphrase from file - passphraseBytes, err := os.ReadFile(passphraseFile) - if err != nil { - return fmt.Errorf("failed to read passphrase from file '%s': %w", passphraseFile, err) - } - passphrase := strings.TrimSpace(string(passphraseBytes)) + // Read passphrase from file + passphraseBytes, err := os.ReadFile(passphraseFile) + if err != nil { + return fmt.Errorf("failed to read passphrase from file '%s': %w", passphraseFile, err) + } + passphrase := strings.TrimSpace(string(passphraseBytes)) - if passphrase == "" { - return fmt.Errorf("passphrase file '%s' is empty", passphraseFile) - } + if passphrase == "" { + return fmt.Errorf("passphrase file '%s' is empty", passphraseFile) + } - // Resolve signer path; allow absolute, relative to node root, or relative to CWD if resolution fails - signerPath, err := filepath.Abs(strings.TrimSuffix(nodeConfig.Signer.SignerPath, "signer.json")) - if err != nil { - return err - } + // Resolve signer path; allow absolute, relative to node root, or relative to CWD if resolution fails + signerPath, err := filepath.Abs(strings.TrimSuffix(nodeConfig.Signer.SignerPath, "signer.json")) + if err != nil { + return err + } - signer, err = file.LoadFileSystemSigner(signerPath, []byte(passphrase)) - if err != nil { - return err + signer, err = file.LoadFileSystemSigner(signerPath, []byte(passphrase)) + if err != nil { + return err + } + case "awskms": + logger.Info().Msg("initializing AWS KMS signer") + var err error + signer, err = awssigner.NewKmsSigner(ctx, nodeConfig.Signer.KmsRegion, nodeConfig.Signer.KmsKeyID) + if err != nil { + return fmt.Errorf("failed to initialize AWS KMS signer: %w", err) + } + default: + return fmt.Errorf("unknown signer type: %s", nodeConfig.Signer.SignerType) } - } else if nodeConfig.Node.Aggregator && nodeConfig.Signer.SignerType != "file" { - return fmt.Errorf("unknown signer type: %s", nodeConfig.Signer.SignerType) } blobClient, err := blobrpc.NewWSClient(ctx, nodeConfig.DA.Address, nodeConfig.DA.AuthToken, "") diff --git a/pkg/config/config.go b/pkg/config/config.go index 2ae3f0ff2d..867a51dbdf 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -140,6 +140,10 @@ const ( FlagSignerType = FlagPrefixEvnode + "signer.signer_type" // FlagSignerPath is a flag for specifying the signer path FlagSignerPath = FlagPrefixEvnode + "signer.signer_path" + // FlagSignerKmsKeyID is a flag for specifying the KMS key ID + FlagSignerKmsKeyID = FlagPrefixEvnode + "signer.kms_key_id" + // FlagSignerKmsRegion is a flag for specifying the KMS region + FlagSignerKmsRegion = FlagPrefixEvnode + "signer.kms_region" // FlagSignerPassphraseFile is a flag for specifying the file containing the signer passphrase FlagSignerPassphraseFile = FlagPrefixEvnode + "signer.passphrase_file" @@ -292,8 +296,10 @@ type P2PConfig struct { // SignerConfig contains all signer configuration parameters type SignerConfig struct { - SignerType string `mapstructure:"signer_type" yaml:"signer_type" comment:"Type of remote signer to use (file, grpc)"` + SignerType string `mapstructure:"signer_type" yaml:"signer_type" comment:"Type of remote signer to use (file, grpc, awskms)"` SignerPath string `mapstructure:"signer_path" yaml:"signer_path" comment:"Path to the signer file or address"` + KmsKeyID string `mapstructure:"kms_key_id" yaml:"kms_key_id" comment:"AWS KMS Key ID or ARN for awskms signer"` + KmsRegion string `mapstructure:"kms_region" yaml:"kms_region" comment:"AWS Region for awskms signer"` } // RPCConfig contains all RPC server configuration parameters @@ -548,8 +554,10 @@ func AddFlags(cmd *cobra.Command) { cmd.Flags().Float64(FlagTracingSampleRate, instrDef.TracingSampleRate, "trace sampling rate (0.0-1.0)") // Signer configuration flags - cmd.Flags().String(FlagSignerType, def.Signer.SignerType, "type of signer to use (file, grpc)") + cmd.Flags().String(FlagSignerType, def.Signer.SignerType, "type of signer to use (file, grpc, awskms)") cmd.Flags().String(FlagSignerPath, def.Signer.SignerPath, "path to the signer file or address") + cmd.Flags().String(FlagSignerKmsKeyID, def.Signer.KmsKeyID, "AWS KMS Key ID or ARN for awskms signer") + cmd.Flags().String(FlagSignerKmsRegion, def.Signer.KmsRegion, "AWS Region for awskms signer") cmd.Flags().String(FlagSignerPassphraseFile, "", "path to file containing the signer passphrase (required for file signer and if aggregator is enabled)") cmd.MarkFlagsMutuallyExclusive(FlagLight, FlagAggregator) diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index ae6be38118..b78cab8ad9 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -107,6 +107,8 @@ func TestAddFlags(t *testing.T) { assertFlagValue(t, flags, FlagSignerPassphraseFile, "") assertFlagValue(t, flags, FlagSignerType, "file") assertFlagValue(t, flags, FlagSignerPath, DefaultConfig().Signer.SignerPath) + assertFlagValue(t, flags, FlagSignerKmsKeyID, DefaultConfig().Signer.KmsKeyID) + assertFlagValue(t, flags, FlagSignerKmsRegion, DefaultConfig().Signer.KmsRegion) // RPC flags assertFlagValue(t, flags, FlagRPCAddress, DefaultConfig().RPC.Address) @@ -118,7 +120,7 @@ func TestAddFlags(t *testing.T) { assertFlagValue(t, flags, FlagPruningInterval, DefaultConfig().Pruning.Interval.Duration) // Count the number of flags we're explicitly checking - expectedFlagCount := 67 // Update this number if you add more flag checks above + expectedFlagCount := 69 // Update this number if you add more flag checks above // Get the actual number of flags (both regular and persistent) actualFlagCount := 0 diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index 4b959a0f95..4009e5dc50 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -93,6 +93,8 @@ func DefaultConfig() Config { Signer: SignerConfig{ SignerType: "file", SignerPath: "config", + KmsKeyID: "", + KmsRegion: "", }, RPC: RPCConfig{ Address: "127.0.0.1:7331", diff --git a/pkg/signer/aws/signer.go b/pkg/signer/aws/signer.go new file mode 100644 index 0000000000..7342be9f83 --- /dev/null +++ b/pkg/signer/aws/signer.go @@ -0,0 +1,163 @@ +// Package aws implements a signer.Signer backed by AWS KMS. +// It delegates signing to a remote KMS key and caches the public key locally. +package aws + +import ( + "context" + "crypto/ed25519" + "crypto/sha256" + "crypto/x509" + "fmt" + "sync" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/kms" + "github.com/aws/aws-sdk-go-v2/service/kms/types" + "github.com/libp2p/go-libp2p/core/crypto" + + "github.com/evstack/ev-node/pkg/signer" +) + +// KMSClient is the subset of the AWS KMS client API that KmsSigner needs. +// This allows mocking in tests. +type KMSClient interface { + Sign(ctx context.Context, params *kms.SignInput, optFns ...func(*kms.Options)) (*kms.SignOutput, error) + GetPublicKey(ctx context.Context, params *kms.GetPublicKeyInput, optFns ...func(*kms.Options)) (*kms.GetPublicKeyOutput, error) +} + +// KmsSigner implements the signer.Signer interface using AWS KMS. +type KmsSigner struct { + client KMSClient + keyID string + mu sync.RWMutex + publicKey crypto.PubKey + address []byte +} + +var _ signer.Signer = (*KmsSigner)(nil) + +// NewKmsSigner creates a new Signer backed by an AWS KMS Ed25519 key. +// It uses the standard AWS credential chain (env vars, ~/.aws/credentials, IAM roles, etc.). +func NewKmsSigner(ctx context.Context, region string, keyID string) (*KmsSigner, error) { + if keyID == "" { + return nil, fmt.Errorf("aws kms key ID is required") + } + + cfgOpts := []func(*awsconfig.LoadOptions) error{} + if region != "" { + cfgOpts = append(cfgOpts, awsconfig.WithRegion(region)) + } + + cfg, err := awsconfig.LoadDefaultConfig(ctx, cfgOpts...) + if err != nil { + return nil, fmt.Errorf("failed to load AWS config: %w", err) + } + + client := kms.NewFromConfig(cfg) + return NewKmsSignerFromClient(ctx, client, keyID) +} + +// NewKmsSignerFromClient creates a KmsSigner from an existing KMS client. +// Useful for testing with a mock client. +func NewKmsSignerFromClient(ctx context.Context, client KMSClient, keyID string) (*KmsSigner, error) { + if keyID == "" { + return nil, fmt.Errorf("aws kms key ID is required") + } + + s := &KmsSigner{ + client: client, + keyID: keyID, + } + + // Fetch and cache the public key eagerly so we fail fast on misconfiguration. + if err := s.fetchPublicKey(ctx); err != nil { + return nil, fmt.Errorf("failed to fetch public key from KMS: %w", err) + } + + return s, nil +} + +// fetchPublicKey retrieves the public key from KMS and caches it. +func (s *KmsSigner) fetchPublicKey(ctx context.Context) error { + out, err := s.client.GetPublicKey(ctx, &kms.GetPublicKeyInput{ + KeyId: aws.String(s.keyID), + }) + if err != nil { + return fmt.Errorf("KMS GetPublicKey failed: %w", err) + } + + // AWS returns the public key as a DER-encoded X.509 SubjectPublicKeyInfo. + pub, err := x509.ParsePKIXPublicKey(out.PublicKey) + if err != nil { + return fmt.Errorf("failed to parse KMS public key: %w", err) + } + + edPubKey, ok := pub.(ed25519.PublicKey) + if !ok { + return fmt.Errorf("unsupported key type from KMS: expected ed25519, got %T", pub) + } + + cryptoPubKey, err := crypto.UnmarshalEd25519PublicKey(edPubKey) + if err != nil { + return fmt.Errorf("failed to convert to libp2p pubkey: %w", err) + } + + bz, err := cryptoPubKey.Raw() + if err != nil { + return fmt.Errorf("failed to get raw pubkey bytes: %w", err) + } + + address := sha256.Sum256(bz) + + s.mu.Lock() + defer s.mu.Unlock() + + s.publicKey = cryptoPubKey + s.address = address[:] + + return nil +} + +// Sign signs a message using the remote KMS key. +func (s *KmsSigner) Sign(message []byte) ([]byte, error) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + out, err := s.client.Sign(ctx, &kms.SignInput{ + KeyId: aws.String(s.keyID), + Message: message, + MessageType: types.MessageTypeRaw, + SigningAlgorithm: types.SigningAlgorithmSpecEd25519Sha512, + }) + if err != nil { + return nil, fmt.Errorf("KMS Sign failed: %w", err) + } + + return out.Signature, nil +} + +// GetPublic returns the cached public key. +func (s *KmsSigner) GetPublic() (crypto.PubKey, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + if s.publicKey == nil { + return nil, fmt.Errorf("public key not loaded") + } + + return s.publicKey, nil +} + +// GetAddress returns the cached address derived from the public key. +func (s *KmsSigner) GetAddress() ([]byte, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + if s.address == nil { + return nil, fmt.Errorf("address not loaded") + } + + return s.address, nil +} diff --git a/pkg/signer/aws/signer_test.go b/pkg/signer/aws/signer_test.go new file mode 100644 index 0000000000..5ca4c0133c --- /dev/null +++ b/pkg/signer/aws/signer_test.go @@ -0,0 +1,156 @@ +package aws + +import ( + "context" + "crypto/ed25519" + "crypto/x509" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/kms" + "github.com/aws/aws-sdk-go-v2/service/kms/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// mockKMSClient is a test double implementing KMSClient. +type mockKMSClient struct { + pubKeyDER []byte + signFn func(ctx context.Context, params *kms.SignInput) (*kms.SignOutput, error) + getPubFn func(ctx context.Context, params *kms.GetPublicKeyInput) (*kms.GetPublicKeyOutput, error) +} + +func (m *mockKMSClient) Sign(ctx context.Context, params *kms.SignInput, _ ...func(*kms.Options)) (*kms.SignOutput, error) { + if m.signFn != nil { + return m.signFn(ctx, params) + } + return &kms.SignOutput{Signature: []byte("mock-signature")}, nil +} + +func (m *mockKMSClient) GetPublicKey(ctx context.Context, params *kms.GetPublicKeyInput, _ ...func(*kms.Options)) (*kms.GetPublicKeyOutput, error) { + if m.getPubFn != nil { + return m.getPubFn(ctx, params) + } + return &kms.GetPublicKeyOutput{ + PublicKey: m.pubKeyDER, + }, nil +} + +// generateTestEd25519DER generates an Ed25519 key pair and returns +// the public key in DER (X.509 SubjectPublicKeyInfo) format. +func generateTestEd25519DER(t *testing.T) (ed25519.PublicKey, []byte) { + t.Helper() + pub, _, err := ed25519.GenerateKey(nil) + require.NoError(t, err) + + der, err := x509.MarshalPKIXPublicKey(pub) + require.NoError(t, err) + return pub, der +} + +func TestNewKmsSignerFromClient_Success(t *testing.T) { + _, der := generateTestEd25519DER(t) + + mock := &mockKMSClient{pubKeyDER: der} + s, err := NewKmsSignerFromClient(context.Background(), mock, "arn:aws:kms:us-east-1:123456789012:key/test-key-id") + require.NoError(t, err) + require.NotNil(t, s) + + // Verify public key was cached + pubKey, err := s.GetPublic() + require.NoError(t, err) + require.NotNil(t, pubKey) + + // Verify address was cached + addr, err := s.GetAddress() + require.NoError(t, err) + assert.Len(t, addr, 32) // sha256 output +} + +func TestNewKmsSignerFromClient_EmptyKeyID(t *testing.T) { + _, err := NewKmsSignerFromClient(context.Background(), &mockKMSClient{}, "") + require.Error(t, err) + assert.Contains(t, err.Error(), "key ID is required") +} + +func TestNewKmsSignerFromClient_GetPublicKeyFails(t *testing.T) { + mock := &mockKMSClient{ + getPubFn: func(_ context.Context, _ *kms.GetPublicKeyInput) (*kms.GetPublicKeyOutput, error) { + return nil, fmt.Errorf("access denied") + }, + } + + _, err := NewKmsSignerFromClient(context.Background(), mock, "test-key") + require.Error(t, err) + assert.Contains(t, err.Error(), "access denied") +} + +func TestSign_Success(t *testing.T) { + _, der := generateTestEd25519DER(t) + + expectedSig := []byte("test-signature-bytes") + mock := &mockKMSClient{ + pubKeyDER: der, + signFn: func(_ context.Context, params *kms.SignInput) (*kms.SignOutput, error) { + assert.Equal(t, types.MessageTypeRaw, params.MessageType) + assert.Equal(t, types.SigningAlgorithmSpecEd25519Sha512, params.SigningAlgorithm) + return &kms.SignOutput{Signature: expectedSig}, nil + }, + } + + s, err := NewKmsSignerFromClient(context.Background(), mock, "test-key") + require.NoError(t, err) + + sig, err := s.Sign([]byte("hello world")) + require.NoError(t, err) + assert.Equal(t, expectedSig, sig) +} + +func TestSign_KMSFailure(t *testing.T) { + _, der := generateTestEd25519DER(t) + + mock := &mockKMSClient{ + pubKeyDER: der, + signFn: func(_ context.Context, _ *kms.SignInput) (*kms.SignOutput, error) { + return nil, fmt.Errorf("throttling exception") + }, + } + + s, err := NewKmsSignerFromClient(context.Background(), mock, "test-key") + require.NoError(t, err) + + _, err = s.Sign([]byte("hello world")) + require.Error(t, err) + assert.Contains(t, err.Error(), "KMS Sign failed") +} + +func TestGetPublic_Cached(t *testing.T) { + pub, der := generateTestEd25519DER(t) + + mock := &mockKMSClient{pubKeyDER: der} + s, err := NewKmsSignerFromClient(context.Background(), mock, "test-key") + require.NoError(t, err) + + cryptoPub, err := s.GetPublic() + require.NoError(t, err) + + raw, err := cryptoPub.Raw() + require.NoError(t, err) + assert.Equal(t, []byte(pub), raw) +} + +func TestGetAddress_Deterministic(t *testing.T) { + _, der := generateTestEd25519DER(t) + + mock := &mockKMSClient{pubKeyDER: der} + s, err := NewKmsSignerFromClient(context.Background(), mock, "test-key") + require.NoError(t, err) + + addr1, err := s.GetAddress() + require.NoError(t, err) + + addr2, err := s.GetAddress() + require.NoError(t, err) + + assert.Equal(t, addr1, addr2, "address should be deterministic") +} From ec30921f30322232cd6d507ffc54a404be2629ed Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Mon, 16 Mar 2026 18:24:08 +0100 Subject: [PATCH 2/3] Extend kms --- block/internal/executing/executor.go | 6 +- block/internal/submitting/da_submitter.go | 21 +-- .../da_submitter_integration_test.go | 4 +- .../internal/submitting/da_submitter_test.go | 2 +- .../syncing/da_retriever_strict_test.go | 7 +- block/internal/syncing/da_retriever_test.go | 2 +- block/internal/syncing/p2p_handler_test.go | 10 +- block/internal/syncing/syncer_test.go | 2 +- pkg/cmd/init.go | 69 +++------ pkg/cmd/run_node.go | 39 ++--- pkg/config/config.go | 31 +++- pkg/config/config_test.go | 6 +- pkg/config/defaults.go | 4 + pkg/signer/aws/signer.go | 136 ++++++++++++++---- pkg/signer/aws/signer_test.go | 18 +-- pkg/signer/factory/factory.go | 57 ++++++++ pkg/signer/file/README.md | 2 +- pkg/signer/file/doc.go | 2 +- pkg/signer/file/example_test.go | 5 +- pkg/signer/file/file_signer_test.go | 11 +- pkg/signer/file/local.go | 3 +- pkg/signer/noop/signer.go | 3 +- pkg/signer/noop/signer_test.go | 5 +- pkg/signer/signer.go | 5 +- pkg/sync/sync_service_test.go | 2 +- types/utils.go | 5 +- 26 files changed, 298 insertions(+), 159 deletions(-) create mode 100644 pkg/signer/factory/factory.go diff --git a/block/internal/executing/executor.go b/block/internal/executing/executor.go index 42621c024c..0f5ed5fa9f 100644 --- a/block/internal/executing/executor.go +++ b/block/internal/executing/executor.go @@ -542,7 +542,7 @@ func (e *Executor) ProduceBlock(ctx context.Context) error { // signing the header is done after applying the block // as for signing, the state of the block may be required by the signature payload provider. // For based sequencer, this will return an empty signature. - signature, _, err := e.signHeader(&header.Header) + signature, _, err := e.signHeader(ctx, &header.Header) if err != nil { return fmt.Errorf("failed to sign header: %w", err) } @@ -821,7 +821,7 @@ func (e *Executor) ApplyBlock(ctx context.Context, header types.Header, data *ty // signHeader signs the block header and returns both the signature and the // serialized header bytes (signing payload). The caller can reuse headerBytes // in SaveBlockDataFromBytes to avoid a redundant MarshalBinary call. -func (e *Executor) signHeader(header *types.Header) (types.Signature, []byte, error) { +func (e *Executor) signHeader(ctx context.Context, header *types.Header) (types.Signature, []byte, error) { // For based sequencer, return empty signature as there is no signer if e.signer == nil { return types.Signature{}, nil, nil @@ -832,7 +832,7 @@ func (e *Executor) signHeader(header *types.Header) (types.Signature, []byte, er return nil, nil, fmt.Errorf("failed to get signature payload: %w", err) } - sig, err := e.signer.Sign(bz) + sig, err := e.signer.Sign(ctx, bz) if err != nil { return nil, nil, err } diff --git a/block/internal/submitting/da_submitter.go b/block/internal/submitting/da_submitter.go index 4720d7788f..1f7edbed8d 100644 --- a/block/internal/submitting/da_submitter.go +++ b/block/internal/submitting/da_submitter.go @@ -225,7 +225,7 @@ func (s *DASubmitter) SubmitHeaders(ctx context.Context, headers []*types.Signed s.logger.Info().Int("count", len(headers)).Msg("submitting headers to DA") // Create DA envelopes with parallel signing and caching - envelopes, err := s.createDAEnvelopes(headers, marshalledHeaders, signer) + envelopes, err := s.createDAEnvelopes(ctx, headers, marshalledHeaders, signer) if err != nil { return err } @@ -256,7 +256,7 @@ func (s *DASubmitter) SubmitHeaders(ctx context.Context, headers []*types.Signed // createDAEnvelopes creates signed DA envelopes for the given headers. // It uses caching to avoid re-signing on retries and parallel signing for new envelopes. -func (s *DASubmitter) createDAEnvelopes(headers []*types.SignedHeader, marshalledHeaders [][]byte, signer signer.Signer) ([][]byte, error) { +func (s *DASubmitter) createDAEnvelopes(ctx context.Context, headers []*types.SignedHeader, marshalledHeaders [][]byte, signer signer.Signer) ([][]byte, error) { envelopes := make([][]byte, len(headers)) // First pass: check cache for already-signed envelopes @@ -284,7 +284,7 @@ func (s *DASubmitter) createDAEnvelopes(headers []*types.SignedHeader, marshalle // For small batches, sign sequentially to avoid goroutine overhead if len(needSigning) <= 2 || s.signingWorkers <= 1 { for _, i := range needSigning { - envelope, err := s.signAndCacheEnvelope(headers[i], marshalledHeaders[i], signer) + envelope, err := s.signAndCacheEnvelope(ctx, headers[i], marshalledHeaders[i], signer) if err != nil { return nil, fmt.Errorf("failed to create envelope for header %d: %w", i, err) } @@ -294,11 +294,12 @@ func (s *DASubmitter) createDAEnvelopes(headers []*types.SignedHeader, marshalle } // Parallel signing for larger batches - return s.signEnvelopesParallel(headers, marshalledHeaders, envelopes, needSigning, signer) + return s.signEnvelopesParallel(ctx, headers, marshalledHeaders, envelopes, needSigning, signer) } // signEnvelopesParallel signs envelopes in parallel using a worker pool. func (s *DASubmitter) signEnvelopesParallel( + ctx context.Context, headers []*types.SignedHeader, marshalledHeaders [][]byte, envelopes [][]byte, @@ -323,7 +324,7 @@ func (s *DASubmitter) signEnvelopesParallel( for range numWorkers { wg.Go(func() { for job := range jobs { - envelope, err := s.signAndCacheEnvelope(headers[job.index], marshalledHeaders[job.index], signer) + envelope, err := s.signAndCacheEnvelope(ctx, headers[job.index], marshalledHeaders[job.index], signer) results <- signResult{index: job.index, envelope: envelope, err: err} } }) @@ -361,9 +362,9 @@ func (s *DASubmitter) signEnvelopesParallel( } // signAndCacheEnvelope signs a single header and caches the result. -func (s *DASubmitter) signAndCacheEnvelope(header *types.SignedHeader, marshalledHeader []byte, signer signer.Signer) ([]byte, error) { +func (s *DASubmitter) signAndCacheEnvelope(ctx context.Context, header *types.SignedHeader, marshalledHeader []byte, signer signer.Signer) ([]byte, error) { // Sign the pre-marshalled header content - envelopeSignature, err := signer.Sign(marshalledHeader) + envelopeSignature, err := signer.Sign(ctx, marshalledHeader) if err != nil { return nil, fmt.Errorf("failed to sign envelope: %w", err) } @@ -426,7 +427,7 @@ func (s *DASubmitter) SubmitData(ctx context.Context, unsignedDataList []*types. } // Sign the data (cache returns unsigned SignedData structs) - signedDataList, signedDataListBz, err := s.signData(unsignedDataList, marshalledData, signer, genesis) + signedDataList, signedDataListBz, err := s.signData(ctx, unsignedDataList, marshalledData, signer, genesis) if err != nil { return fmt.Errorf("failed to sign data: %w", err) } @@ -460,7 +461,7 @@ func (s *DASubmitter) SubmitData(ctx context.Context, unsignedDataList []*types. } // signData signs unsigned SignedData structs returned from cache -func (s *DASubmitter) signData(unsignedDataList []*types.SignedData, unsignedDataListBz [][]byte, signer signer.Signer, genesis genesis.Genesis) ([]*types.SignedData, [][]byte, error) { +func (s *DASubmitter) signData(ctx context.Context, unsignedDataList []*types.SignedData, unsignedDataListBz [][]byte, signer signer.Signer, genesis genesis.Genesis) ([]*types.SignedData, [][]byte, error) { if signer == nil { return nil, nil, fmt.Errorf("signer is nil") } @@ -493,7 +494,7 @@ func (s *DASubmitter) signData(unsignedDataList []*types.SignedData, unsignedDat continue } - signature, err := signer.Sign(unsignedDataListBz[i]) + signature, err := signer.Sign(ctx, unsignedDataListBz[i]) if err != nil { return nil, nil, fmt.Errorf("failed to sign data: %w", err) } diff --git a/block/internal/submitting/da_submitter_integration_test.go b/block/internal/submitting/da_submitter_integration_test.go index bb8d0342ff..fafe4f044c 100644 --- a/block/internal/submitting/da_submitter_integration_test.go +++ b/block/internal/submitting/da_submitter_integration_test.go @@ -53,7 +53,7 @@ func TestDASubmitter_SubmitHeadersAndData_MarksInclusionAndUpdatesLastSubmitted( hdr1 := &types.SignedHeader{Header: types.Header{BaseHeader: types.BaseHeader{ChainID: gen.ChainID, Height: 1, Time: uint64(time.Now().UnixNano())}, AppHash: stateRoot, ProposerAddress: addr}, Signer: types.Signer{PubKey: pub, Address: addr}} bz1, err := types.DefaultAggregatorNodeSignatureBytesProvider(&hdr1.Header) require.NoError(t, err) - sig1, err := n.Sign(bz1) + sig1, err := n.Sign(context.Background(), bz1) require.NoError(t, err) hdr1.Signature = sig1 data1 := &types.Data{Metadata: &types.Metadata{ChainID: gen.ChainID, Height: 1, Time: uint64(time.Now().UnixNano())}, Txs: types.Txs{types.Tx("a")}} @@ -61,7 +61,7 @@ func TestDASubmitter_SubmitHeadersAndData_MarksInclusionAndUpdatesLastSubmitted( hdr2 := &types.SignedHeader{Header: types.Header{BaseHeader: types.BaseHeader{ChainID: gen.ChainID, Height: 2, Time: uint64(time.Now().Add(time.Second).UnixNano())}, AppHash: stateRoot, ProposerAddress: addr}, Signer: types.Signer{PubKey: pub, Address: addr}} bz2, err := types.DefaultAggregatorNodeSignatureBytesProvider(&hdr2.Header) require.NoError(t, err) - sig2, err := n.Sign(bz2) + sig2, err := n.Sign(context.Background(), bz2) require.NoError(t, err) hdr2.Signature = sig2 data2 := &types.Data{Metadata: &types.Metadata{ChainID: gen.ChainID, Height: 2, Time: uint64(time.Now().Add(time.Second).UnixNano())}, Txs: types.Txs{types.Tx("b")}} diff --git a/block/internal/submitting/da_submitter_test.go b/block/internal/submitting/da_submitter_test.go index 67967dbe19..defe7e4d73 100644 --- a/block/internal/submitting/da_submitter_test.go +++ b/block/internal/submitting/da_submitter_test.go @@ -175,7 +175,7 @@ func TestDASubmitter_SubmitHeaders_Success(t *testing.T) { for _, header := range []*types.SignedHeader{header1, header2} { bz, err := types.DefaultAggregatorNodeSignatureBytesProvider(&header.Header) require.NoError(t, err) - sig, err := signer.Sign(bz) + sig, err := signer.Sign(context.Background(), bz) require.NoError(t, err) header.Signature = sig } diff --git a/block/internal/syncing/da_retriever_strict_test.go b/block/internal/syncing/da_retriever_strict_test.go index a6d5140d97..4760463577 100644 --- a/block/internal/syncing/da_retriever_strict_test.go +++ b/block/internal/syncing/da_retriever_strict_test.go @@ -1,6 +1,7 @@ package syncing import ( + "context" "testing" "time" @@ -31,7 +32,7 @@ func TestDARetriever_StrictEnvelopeMode_Switch(t *testing.T) { // Sign it bz, err := types.DefaultAggregatorNodeSignatureBytesProvider(&legacyHeader.Header) require.NoError(t, err) - sig, err := signer.Sign(bz) + sig, err := signer.Sign(context.Background(), bz) require.NoError(t, err) legacyHeader.Signature = sig @@ -50,7 +51,7 @@ func TestDARetriever_StrictEnvelopeMode_Switch(t *testing.T) { // Sign content bz2, err := types.DefaultAggregatorNodeSignatureBytesProvider(&envelopeHeader.Header) require.NoError(t, err) - sig2, err := signer.Sign(bz2) + sig2, err := signer.Sign(context.Background(), bz2) require.NoError(t, err) envelopeHeader.Signature = sig2 @@ -61,7 +62,7 @@ func TestDARetriever_StrictEnvelopeMode_Switch(t *testing.T) { contentBytes, err := envelopeHeader.MarshalBinary() require.NoError(t, err) // Sign envelope - envSig, err := signer.Sign(contentBytes) + envSig, err := signer.Sign(context.Background(), contentBytes) require.NoError(t, err) // Marshal to envelope envelopeBlob, err := envelopeHeader.MarshalDAEnvelope(envSig) diff --git a/block/internal/syncing/da_retriever_test.go b/block/internal/syncing/da_retriever_test.go index cbb527488d..a6922a3739 100644 --- a/block/internal/syncing/da_retriever_test.go +++ b/block/internal/syncing/da_retriever_test.go @@ -71,7 +71,7 @@ func makeSignedDataBytesWithTime(t *testing.T, chainID string, height uint64, pr // For DA SignedData, sign the Data payload bytes (matches DA submission logic) payload, _ := d.MarshalBinary() - sig, err := signer.Sign(payload) + sig, err := signer.Sign(context.Background(), payload) require.NoError(t, err) sd := &types.SignedData{Data: *d, Signature: sig, Signer: types.Signer{PubKey: pub, Address: proposer}} bin, err := sd.MarshalBinary() diff --git a/block/internal/syncing/p2p_handler_test.go b/block/internal/syncing/p2p_handler_test.go index a1cc6d993f..879dc089ee 100644 --- a/block/internal/syncing/p2p_handler_test.go +++ b/block/internal/syncing/p2p_handler_test.go @@ -51,7 +51,7 @@ func p2pMakeSignedHeader(t *testing.T, chainID string, height uint64, proposer [ } bz, err := types.DefaultAggregatorNodeSignatureBytesProvider(&hdr.Header) require.NoError(t, err, "failed to get signature bytes for header") - sig, err := signer.Sign(bz) + sig, err := signer.Sign(context.Background(), bz) require.NoError(t, err, "failed to sign header bytes") hdr.Signature = sig return &types.P2PSignedHeader{SignedHeader: hdr} @@ -140,7 +140,7 @@ func TestP2PHandler_ProcessHeight_EmitsEventWhenHeaderAndDataPresent(t *testing. header.DataHash = data.DACommitment() bz, err := types.DefaultAggregatorNodeSignatureBytesProvider(&header.Header) require.NoError(t, err) - sig, err := p.Signer.Sign(bz) + sig, err := p.Signer.Sign(context.Background(), bz) require.NoError(t, err) header.Signature = sig @@ -166,7 +166,7 @@ func TestP2PHandler_ProcessHeight_SkipsWhenDataMissing(t *testing.T) { header.DataHash = data.DACommitment() bz, err := types.DefaultAggregatorNodeSignatureBytesProvider(&header.Header) require.NoError(t, err) - sig, err := p.Signer.Sign(bz) + sig, err := p.Signer.Sign(context.Background(), bz) require.NoError(t, err) header.Signature = sig @@ -236,7 +236,7 @@ func TestP2PHandler_ProcessedHeightSkipsPreviouslyHandledBlocks(t *testing.T) { header.DataHash = data.DACommitment() bz, err := types.DefaultAggregatorNodeSignatureBytesProvider(&header.Header) require.NoError(t, err) - sig, err := p.Signer.Sign(bz) + sig, err := p.Signer.Sign(context.Background(), bz) require.NoError(t, err) header.Signature = sig @@ -259,7 +259,7 @@ func TestP2PHandler_SetProcessedHeightPreventsDuplicates(t *testing.T) { header.DataHash = data.DACommitment() bz, err := types.DefaultAggregatorNodeSignatureBytesProvider(&header.Header) require.NoError(t, err) - sig, err := p.Signer.Sign(bz) + sig, err := p.Signer.Sign(context.Background(), bz) require.NoError(t, err) header.Signature = sig diff --git a/block/internal/syncing/syncer_test.go b/block/internal/syncing/syncer_test.go index 771e71c284..c42024a4b7 100644 --- a/block/internal/syncing/syncer_test.go +++ b/block/internal/syncing/syncer_test.go @@ -78,7 +78,7 @@ func makeSignedHeaderBytes( } bz, err := types.DefaultAggregatorNodeSignatureBytesProvider(&hdr.Header) require.NoError(tb, err) - sig, err := signer.Sign(bz) + sig, err := signer.Sign(context.Background(), bz) require.NoError(tb, err) hdr.Signature = sig bin, err := hdr.MarshalBinary() diff --git a/pkg/cmd/init.go b/pkg/cmd/init.go index 79201a63b2..8a231b8b4d 100644 --- a/pkg/cmd/init.go +++ b/pkg/cmd/init.go @@ -6,11 +6,11 @@ import ( "os" "path/filepath" + rollconf "github.com/evstack/ev-node/pkg/config" rollconf "github.com/evstack/ev-node/pkg/config" "github.com/evstack/ev-node/pkg/hash" "github.com/evstack/ev-node/pkg/p2p/key" - awssigner "github.com/evstack/ev-node/pkg/signer/aws" - "github.com/evstack/ev-node/pkg/signer/file" + "github.com/evstack/ev-node/pkg/signer/factory" ) // CreateSigner sets up the signer configuration and creates necessary files @@ -19,61 +19,28 @@ func CreateSigner(config *rollconf.Config, homePath string, passphrase string) ( return nil, nil } - switch config.Signer.SignerType { - case "file": - if passphrase == "" { - return nil, fmt.Errorf("passphrase is required when using local file signer") - } - + if config.Signer.SignerType == "file" { signerDir := filepath.Join(homePath, "config") - if err := os.MkdirAll(signerDir, 0o750); err != nil { - return nil, fmt.Errorf("failed to create signer directory: %w", err) - } - config.Signer.SignerPath = signerDir + } - signer, err := file.CreateFileSystemSigner(config.Signer.SignerPath, []byte(passphrase)) - if err != nil { - return nil, fmt.Errorf("failed to initialize signer: %w", err) - } - - pubKey, err := signer.GetPublic() - if err != nil { - return nil, fmt.Errorf("failed to get public key: %w", err) - } - - bz, err := pubKey.Raw() - if err != nil { - return nil, fmt.Errorf("failed to get public key raw bytes: %w", err) - } - - proposerAddress := hash.SumTruncated(bz) - return proposerAddress, nil - - case "awskms": - // For KMS, the key is pre-provisioned in AWS. We fetch the public key - // to derive the proposer address. - signer, err := awssigner.NewKmsSigner(context.Background(), config.Signer.KmsRegion, config.Signer.KmsKeyID) - if err != nil { - return nil, fmt.Errorf("failed to initialize AWS KMS signer: %w", err) - } - - pubKey, err := signer.GetPublic() - if err != nil { - return nil, fmt.Errorf("failed to get public key from KMS: %w", err) - } - - bz, err := pubKey.Raw() - if err != nil { - return nil, fmt.Errorf("failed to get public key raw bytes: %w", err) - } + signer, err := factory.NewSigner(context.Background(), config, passphrase) + if err != nil { + return nil, fmt.Errorf("failed to initialize signer via factory: %w", err) + } - proposerAddress := hash.SumTruncated(bz) - return proposerAddress, nil + pubKey, err := signer.GetPublic() + if err != nil { + return nil, fmt.Errorf("failed to get public key: %w", err) + } - default: - return nil, fmt.Errorf("unknown signer type: %s", config.Signer.SignerType) + bz, err := pubKey.Raw() + if err != nil { + return nil, fmt.Errorf("failed to get public key raw bytes: %w", err) } + + proposerAddress := hash.SumTruncated(bz) + return proposerAddress, nil } // LoadOrGenNodeKey creates the node key file if it doesn't exist. diff --git a/pkg/cmd/run_node.go b/pkg/cmd/run_node.go index 52ffbdb546..fde7485278 100644 --- a/pkg/cmd/run_node.go +++ b/pkg/cmd/run_node.go @@ -26,8 +26,7 @@ import ( "github.com/evstack/ev-node/pkg/p2p" "github.com/evstack/ev-node/pkg/p2p/key" "github.com/evstack/ev-node/pkg/signer" - awssigner "github.com/evstack/ev-node/pkg/signer/aws" - "github.com/evstack/ev-node/pkg/signer/file" + "github.com/evstack/ev-node/pkg/signer/factory" "github.com/evstack/ev-node/pkg/telemetry" ) @@ -112,9 +111,8 @@ func StartNode( // eagerly over WebSocket if no DA server is running). var signer signer.Signer if nodeConfig.Node.Aggregator && !nodeConfig.Node.BasedSequencer { - switch nodeConfig.Signer.SignerType { - case "file": - // Get passphrase file path + passphrase := "" + if nodeConfig.Signer.SignerType == "file" { passphraseFile, err := cmd.Flags().GetString(rollconf.FlagSignerPassphraseFile) if err != nil { return fmt.Errorf("failed to get '%s' flag: %w", rollconf.FlagSignerPassphraseFile, err) @@ -124,36 +122,25 @@ func StartNode( return fmt.Errorf("passphrase file must be provided via --evnode.signer.passphrase_file") } - // Read passphrase from file passphraseBytes, err := os.ReadFile(passphraseFile) if err != nil { return fmt.Errorf("failed to read passphrase from file '%s': %w", passphraseFile, err) } - passphrase := strings.TrimSpace(string(passphraseBytes)) + passphrase = strings.TrimSpace(string(passphraseBytes)) if passphrase == "" { return fmt.Errorf("passphrase file '%s' is empty", passphraseFile) } + } - // Resolve signer path; allow absolute, relative to node root, or relative to CWD if resolution fails - signerPath, err := filepath.Abs(strings.TrimSuffix(nodeConfig.Signer.SignerPath, "signer.json")) - if err != nil { - return err - } - - signer, err = file.LoadFileSystemSigner(signerPath, []byte(passphrase)) - if err != nil { - return err - } - case "awskms": - logger.Info().Msg("initializing AWS KMS signer") - var err error - signer, err = awssigner.NewKmsSigner(ctx, nodeConfig.Signer.KmsRegion, nodeConfig.Signer.KmsKeyID) - if err != nil { - return fmt.Errorf("failed to initialize AWS KMS signer: %w", err) - } - default: - return fmt.Errorf("unknown signer type: %s", nodeConfig.Signer.SignerType) + var err error + signer, err = factory.NewSigner(ctx, &nodeConfig, passphrase) + if err != nil { + return fmt.Errorf("failed to initialize signer via factory: %w", err) + } + + if nodeConfig.Signer.SignerType == "awskms" { + logger.Info().Msg("initialized AWS KMS signer via factory") } } diff --git a/pkg/config/config.go b/pkg/config/config.go index 867a51dbdf..f88e28c6d9 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -144,6 +144,14 @@ const ( FlagSignerKmsKeyID = FlagPrefixEvnode + "signer.kms_key_id" // FlagSignerKmsRegion is a flag for specifying the KMS region FlagSignerKmsRegion = FlagPrefixEvnode + "signer.kms_region" + // FlagSignerKmsProfile is a flag for specifying the AWS profile + FlagSignerKmsProfile = FlagPrefixEvnode + "signer.kms_profile" + // FlagSignerKmsTimeout is a flag for specifying the KMS sign timeout + FlagSignerKmsTimeout = FlagPrefixEvnode + "signer.kms_timeout" + // FlagSignerKmsMaxRetries is a flag for specifying the KMS sign max retries + FlagSignerKmsMaxRetries = FlagPrefixEvnode + "signer.kms_max_retries" + // FlagSignerKmsCacheTTL is a flag for specifying the KMS public key cache TTL + FlagSignerKmsCacheTTL = FlagPrefixEvnode + "signer.kms_cache_ttl" // FlagSignerPassphraseFile is a flag for specifying the file containing the signer passphrase FlagSignerPassphraseFile = FlagPrefixEvnode + "signer.passphrase_file" @@ -298,8 +306,12 @@ type P2PConfig struct { type SignerConfig struct { SignerType string `mapstructure:"signer_type" yaml:"signer_type" comment:"Type of remote signer to use (file, grpc, awskms)"` SignerPath string `mapstructure:"signer_path" yaml:"signer_path" comment:"Path to the signer file or address"` - KmsKeyID string `mapstructure:"kms_key_id" yaml:"kms_key_id" comment:"AWS KMS Key ID or ARN for awskms signer"` - KmsRegion string `mapstructure:"kms_region" yaml:"kms_region" comment:"AWS Region for awskms signer"` + KmsKeyID string `mapstructure:"kms_key_id" yaml:"kms_key_id" comment:"AWS KMS Key ID or ARN for awskms signer"` + KmsRegion string `mapstructure:"kms_region" yaml:"kms_region" comment:"AWS Region for awskms signer"` + KmsProfile string `mapstructure:"kms_profile" yaml:"kms_profile" comment:"AWS Profile for awskms signer"` + KmsTimeout DurationWrapper `mapstructure:"kms_timeout" yaml:"kms_timeout" comment:"Timeout for individual AWS KMS Sign requests"` + KmsMaxRetries int `mapstructure:"kms_max_retries" yaml:"kms_max_retries" comment:"Maximum number of retries for transient AWS KMS failures"` + KmsCacheTTL DurationWrapper `mapstructure:"kms_cache_ttl" yaml:"kms_cache_ttl" comment:"Time-to-live for caching the AWS KMS public key (0 means cache forever)"` } // RPCConfig contains all RPC server configuration parameters @@ -398,6 +410,17 @@ func (c RaftConfig) Validate() error { // Validate validates the config and ensures that the root directory exists. // It creates the directory if it does not exist. func (c *Config) Validate() error { + if c.Signer.SignerType == "awskms" { + if c.Signer.KmsKeyID == "" { + return errors.New("evnode.signer.kms_key_id is required when signer_type is awskms") + } + if c.Signer.KmsTimeout.Duration <= 0 { + return errors.New("evnode.signer.kms_timeout must be positive") + } + if c.Signer.KmsMaxRetries < 0 { + return errors.New("evnode.signer.kms_max_retries must be non-negative") + } + } if c.RootDir == "" { return fmt.Errorf("root directory cannot be empty") } @@ -558,6 +581,10 @@ func AddFlags(cmd *cobra.Command) { cmd.Flags().String(FlagSignerPath, def.Signer.SignerPath, "path to the signer file or address") cmd.Flags().String(FlagSignerKmsKeyID, def.Signer.KmsKeyID, "AWS KMS Key ID or ARN for awskms signer") cmd.Flags().String(FlagSignerKmsRegion, def.Signer.KmsRegion, "AWS Region for awskms signer") + cmd.Flags().String(FlagSignerKmsProfile, def.Signer.KmsProfile, "AWS Profile for awskms signer") + cmd.Flags().Duration(FlagSignerKmsTimeout, def.Signer.KmsTimeout.Duration, "Timeout for individual AWS KMS Sign requests") + cmd.Flags().Int(FlagSignerKmsMaxRetries, def.Signer.KmsMaxRetries, "Maximum number of retries for transient AWS KMS failures") + cmd.Flags().Duration(FlagSignerKmsCacheTTL, def.Signer.KmsCacheTTL.Duration, "Time-to-live for caching the AWS KMS public key (0 means cache forever)") cmd.Flags().String(FlagSignerPassphraseFile, "", "path to file containing the signer passphrase (required for file signer and if aggregator is enabled)") cmd.MarkFlagsMutuallyExclusive(FlagLight, FlagAggregator) diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index b78cab8ad9..3d82688553 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -109,6 +109,10 @@ func TestAddFlags(t *testing.T) { assertFlagValue(t, flags, FlagSignerPath, DefaultConfig().Signer.SignerPath) assertFlagValue(t, flags, FlagSignerKmsKeyID, DefaultConfig().Signer.KmsKeyID) assertFlagValue(t, flags, FlagSignerKmsRegion, DefaultConfig().Signer.KmsRegion) + assertFlagValue(t, flags, FlagSignerKmsProfile, DefaultConfig().Signer.KmsProfile) + assertFlagValue(t, flags, FlagSignerKmsTimeout, DefaultConfig().Signer.KmsTimeout.Duration) + assertFlagValue(t, flags, FlagSignerKmsMaxRetries, DefaultConfig().Signer.KmsMaxRetries) + assertFlagValue(t, flags, FlagSignerKmsCacheTTL, DefaultConfig().Signer.KmsCacheTTL.Duration) // RPC flags assertFlagValue(t, flags, FlagRPCAddress, DefaultConfig().RPC.Address) @@ -120,7 +124,7 @@ func TestAddFlags(t *testing.T) { assertFlagValue(t, flags, FlagPruningInterval, DefaultConfig().Pruning.Interval.Duration) // Count the number of flags we're explicitly checking - expectedFlagCount := 69 // Update this number if you add more flag checks above + expectedFlagCount := 73 // Update this number if you add more flag checks above // Get the actual number of flags (both regular and persistent) actualFlagCount := 0 diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index 4009e5dc50..afde0a67ab 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -95,6 +95,10 @@ func DefaultConfig() Config { SignerPath: "config", KmsKeyID: "", KmsRegion: "", + KmsProfile: "", + KmsTimeout: DurationWrapper{10 * time.Second}, + KmsMaxRetries: 3, + KmsCacheTTL: DurationWrapper{0}, }, RPC: RPCConfig{ Address: "127.0.0.1:7331", diff --git a/pkg/signer/aws/signer.go b/pkg/signer/aws/signer.go index 7342be9f83..22cb9973ca 100644 --- a/pkg/signer/aws/signer.go +++ b/pkg/signer/aws/signer.go @@ -27,20 +27,54 @@ type KMSClient interface { GetPublicKey(ctx context.Context, params *kms.GetPublicKeyInput, optFns ...func(*kms.Options)) (*kms.GetPublicKeyOutput, error) } +// Options configures optional KmsSigner behaviour. +type Options struct { + // Timeout for individual KMS Sign API calls. Default: 10s. + Timeout time.Duration + // MaxRetries for transient KMS failures during Sign. Default: 3. + MaxRetries int + // CacheTTL controls how long the public key is cached before being + // re-fetched from KMS. 0 (default) means cache forever. + CacheTTL time.Duration +} + +func (o *Options) timeout() time.Duration { + if o != nil && o.Timeout > 0 { + return o.Timeout + } + return 10 * time.Second +} + +func (o *Options) maxRetries() int { + if o != nil && o.MaxRetries > 0 { + return o.MaxRetries + } + return 3 +} + +func (o *Options) cacheTTL() time.Duration { + if o != nil { + return o.CacheTTL + } + return 0 +} + // KmsSigner implements the signer.Signer interface using AWS KMS. type KmsSigner struct { - client KMSClient - keyID string - mu sync.RWMutex - publicKey crypto.PubKey - address []byte + client KMSClient + keyID string + opts Options + mu sync.RWMutex + pubKey crypto.PubKey + address []byte + cacheAt time.Time } var _ signer.Signer = (*KmsSigner)(nil) // NewKmsSigner creates a new Signer backed by an AWS KMS Ed25519 key. // It uses the standard AWS credential chain (env vars, ~/.aws/credentials, IAM roles, etc.). -func NewKmsSigner(ctx context.Context, region string, keyID string) (*KmsSigner, error) { +func NewKmsSigner(ctx context.Context, region string, profile string, keyID string, opts *Options) (*KmsSigner, error) { if keyID == "" { return nil, fmt.Errorf("aws kms key ID is required") } @@ -49,6 +83,9 @@ func NewKmsSigner(ctx context.Context, region string, keyID string) (*KmsSigner, if region != "" { cfgOpts = append(cfgOpts, awsconfig.WithRegion(region)) } + if profile != "" { + cfgOpts = append(cfgOpts, awsconfig.WithSharedConfigProfile(profile)) + } cfg, err := awsconfig.LoadDefaultConfig(ctx, cfgOpts...) if err != nil { @@ -56,19 +93,25 @@ func NewKmsSigner(ctx context.Context, region string, keyID string) (*KmsSigner, } client := kms.NewFromConfig(cfg) - return NewKmsSignerFromClient(ctx, client, keyID) + return NewKmsSignerFromClient(ctx, client, keyID, opts) } // NewKmsSignerFromClient creates a KmsSigner from an existing KMS client. // Useful for testing with a mock client. -func NewKmsSignerFromClient(ctx context.Context, client KMSClient, keyID string) (*KmsSigner, error) { +func NewKmsSignerFromClient(ctx context.Context, client KMSClient, keyID string, opts *Options) (*KmsSigner, error) { if keyID == "" { return nil, fmt.Errorf("aws kms key ID is required") } + o := Options{} + if opts != nil { + o = *opts + } + s := &KmsSigner{ client: client, keyID: keyID, + opts: o, } // Fetch and cache the public key eagerly so we fail fast on misconfiguration. @@ -114,40 +157,79 @@ func (s *KmsSigner) fetchPublicKey(ctx context.Context) error { s.mu.Lock() defer s.mu.Unlock() - s.publicKey = cryptoPubKey + s.pubKey = cryptoPubKey s.address = address[:] + s.cacheAt = time.Now() return nil } -// Sign signs a message using the remote KMS key. -func (s *KmsSigner) Sign(message []byte) ([]byte, error) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - out, err := s.client.Sign(ctx, &kms.SignInput{ - KeyId: aws.String(s.keyID), - Message: message, - MessageType: types.MessageTypeRaw, - SigningAlgorithm: types.SigningAlgorithmSpecEd25519Sha512, - }) - if err != nil { - return nil, fmt.Errorf("KMS Sign failed: %w", err) +// Sign signs a message using the remote KMS key with configurable timeout +// and retry with exponential backoff. +func (s *KmsSigner) Sign(ctx context.Context, message []byte) ([]byte, error) { + var lastErr error + maxRetries := s.opts.maxRetries() + timeout := s.opts.timeout() + + for attempt := 0; attempt < maxRetries; attempt++ { + if attempt > 0 { + // Exponential backoff: 100ms, 200ms, 400ms, ... + backoff := time.Duration(100< 0 && time.Since(s.cacheAt) > ttl + s.mu.RUnlock() + + if expired { + if err := s.fetchPublicKey(context.Background()); err != nil { + // If refresh fails, return the stale cached key + if pubKey != nil { + return pubKey, nil + } + return nil, fmt.Errorf("failed to refresh public key: %w", err) + } + s.mu.RLock() + pubKey = s.pubKey + s.mu.RUnlock() + } - if s.publicKey == nil { + if pubKey == nil { return nil, fmt.Errorf("public key not loaded") } - return s.publicKey, nil + return pubKey, nil } // GetAddress returns the cached address derived from the public key. diff --git a/pkg/signer/aws/signer_test.go b/pkg/signer/aws/signer_test.go index 5ca4c0133c..fed439b4c1 100644 --- a/pkg/signer/aws/signer_test.go +++ b/pkg/signer/aws/signer_test.go @@ -52,7 +52,7 @@ func TestNewKmsSignerFromClient_Success(t *testing.T) { _, der := generateTestEd25519DER(t) mock := &mockKMSClient{pubKeyDER: der} - s, err := NewKmsSignerFromClient(context.Background(), mock, "arn:aws:kms:us-east-1:123456789012:key/test-key-id") + s, err := NewKmsSignerFromClient(context.Background(), mock, "arn:aws:kms:us-east-1:123456789012:key/test-key-id", nil) require.NoError(t, err) require.NotNil(t, s) @@ -68,7 +68,7 @@ func TestNewKmsSignerFromClient_Success(t *testing.T) { } func TestNewKmsSignerFromClient_EmptyKeyID(t *testing.T) { - _, err := NewKmsSignerFromClient(context.Background(), &mockKMSClient{}, "") + _, err := NewKmsSignerFromClient(context.Background(), &mockKMSClient{}, "", nil) require.Error(t, err) assert.Contains(t, err.Error(), "key ID is required") } @@ -80,7 +80,7 @@ func TestNewKmsSignerFromClient_GetPublicKeyFails(t *testing.T) { }, } - _, err := NewKmsSignerFromClient(context.Background(), mock, "test-key") + _, err := NewKmsSignerFromClient(context.Background(), mock, "test-key", nil) require.Error(t, err) assert.Contains(t, err.Error(), "access denied") } @@ -98,10 +98,10 @@ func TestSign_Success(t *testing.T) { }, } - s, err := NewKmsSignerFromClient(context.Background(), mock, "test-key") + s, err := NewKmsSignerFromClient(context.Background(), mock, "test-key", nil) require.NoError(t, err) - sig, err := s.Sign([]byte("hello world")) + sig, err := s.Sign(context.Background(), []byte("hello world")) require.NoError(t, err) assert.Equal(t, expectedSig, sig) } @@ -116,10 +116,10 @@ func TestSign_KMSFailure(t *testing.T) { }, } - s, err := NewKmsSignerFromClient(context.Background(), mock, "test-key") + s, err := NewKmsSignerFromClient(context.Background(), mock, "test-key", nil) require.NoError(t, err) - _, err = s.Sign([]byte("hello world")) + _, err = s.Sign(context.Background(), []byte("hello world")) require.Error(t, err) assert.Contains(t, err.Error(), "KMS Sign failed") } @@ -128,7 +128,7 @@ func TestGetPublic_Cached(t *testing.T) { pub, der := generateTestEd25519DER(t) mock := &mockKMSClient{pubKeyDER: der} - s, err := NewKmsSignerFromClient(context.Background(), mock, "test-key") + s, err := NewKmsSignerFromClient(context.Background(), mock, "test-key", nil) require.NoError(t, err) cryptoPub, err := s.GetPublic() @@ -143,7 +143,7 @@ func TestGetAddress_Deterministic(t *testing.T) { _, der := generateTestEd25519DER(t) mock := &mockKMSClient{pubKeyDER: der} - s, err := NewKmsSignerFromClient(context.Background(), mock, "test-key") + s, err := NewKmsSignerFromClient(context.Background(), mock, "test-key", nil) require.NoError(t, err) addr1, err := s.GetAddress() diff --git a/pkg/signer/factory/factory.go b/pkg/signer/factory/factory.go new file mode 100644 index 0000000000..0be914db54 --- /dev/null +++ b/pkg/signer/factory/factory.go @@ -0,0 +1,57 @@ +package factory + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + rollconf "github.com/evstack/ev-node/pkg/config" + "github.com/evstack/ev-node/pkg/signer" + awssigner "github.com/evstack/ev-node/pkg/signer/aws" + "github.com/evstack/ev-node/pkg/signer/file" +) + +// NewSigner creates a new Signer based on the configuration. +func NewSigner(ctx context.Context, config *rollconf.Config, passphrase string) (signer.Signer, error) { + switch config.Signer.SignerType { + case "file": + if passphrase == "" { + return nil, fmt.Errorf("passphrase is required when using local file signer") + } + + // Resolve signer path; allow absolute, relative to node root, or relative to CWD if resolution fails + signerPath, err := filepath.Abs(strings.TrimSuffix(config.Signer.SignerPath, "signer.json")) + if err != nil { + return nil, err + } + + // Ensure directory exists for init command cases + if err := os.MkdirAll(signerPath, 0o750); err != nil { + return nil, fmt.Errorf("failed to create signer directory: %w", err) + } + + // This will either create (if it doesn't exist) or load (if it does). + // In a strictly decoupled factory we'd differentiate Create vs Load, but given the + // underlying CreateFileSystemSigner and LoadFileSystemSigner are typically idempotent + // in behavior if files are provided, let's check for existing files: + signerFile := filepath.Join(signerPath, "signer.json") + if _, err := os.Stat(signerFile); os.IsNotExist(err) { + return file.CreateFileSystemSigner(signerPath, []byte(passphrase)) + } + + return file.LoadFileSystemSigner(signerPath, []byte(passphrase)) + + case "awskms": + opts := &awssigner.Options{ + Timeout: config.Signer.KmsTimeout.Duration, + MaxRetries: config.Signer.KmsMaxRetries, + CacheTTL: config.Signer.KmsCacheTTL.Duration, + } + return awssigner.NewKmsSigner(ctx, config.Signer.KmsRegion, config.Signer.KmsProfile, config.Signer.KmsKeyID, opts) + + default: + return nil, fmt.Errorf("unknown signer type: %s", config.Signer.SignerType) + } +} diff --git a/pkg/signer/file/README.md b/pkg/signer/file/README.md index 9936bca874..e2ecd09302 100644 --- a/pkg/signer/file/README.md +++ b/pkg/signer/file/README.md @@ -31,7 +31,7 @@ if err != nil { // Sign a message message := []byte("Message to sign") -signature, err := signer.Sign(message) +signature, err := signer.Sign(context.Background(), message) if err != nil { // Handle error } diff --git a/pkg/signer/file/doc.go b/pkg/signer/file/doc.go index 6c367aa82a..b150c42df4 100644 --- a/pkg/signer/file/doc.go +++ b/pkg/signer/file/doc.go @@ -14,7 +14,7 @@ The keys are stored in a file in the local filesystem. // Sign a message message := []byte("Message to sign") - signature, err := signer.Sign(message) + signature, err := signer.Sign(context.Background(), message) if err != nil { panic(err) } diff --git a/pkg/signer/file/example_test.go b/pkg/signer/file/example_test.go index 433997d9d8..71d2ced7b7 100644 --- a/pkg/signer/file/example_test.go +++ b/pkg/signer/file/example_test.go @@ -1,6 +1,7 @@ package file import ( + "context" "os" "path/filepath" "testing" @@ -31,7 +32,7 @@ func TestFileSystemSigner(t *testing.T) { // Sign a message message := []byte("Hello, world!") - signature, err := signer.Sign(message) + signature, err := signer.Sign(context.Background(), message) require.NoError(t, err) require.NotNil(t, signature) @@ -78,7 +79,7 @@ func Example() { // Sign a message message := []byte("Message to sign") - signature, err := signer.Sign(message) + signature, err := signer.Sign(context.Background(), message) if err != nil { panic(err) } diff --git a/pkg/signer/file/file_signer_test.go b/pkg/signer/file/file_signer_test.go index ec24e07186..513c4783f8 100644 --- a/pkg/signer/file/file_signer_test.go +++ b/pkg/signer/file/file_signer_test.go @@ -2,6 +2,7 @@ package file import ( "bytes" + "context" "encoding/json" "fmt" "os" @@ -174,7 +175,7 @@ func TestSign(t *testing.T) { // Sign a message message := []byte("Hello, Evolve!") - signature, err := signer.Sign(message) + signature, err := signer.Sign(context.Background(), message) require.NoError(t, err) require.NotNil(t, signature) @@ -208,7 +209,7 @@ func TestSign(t *testing.T) { for _, msg := range messages { message := []byte(msg) - signature, err := signer.Sign(message) + signature, err := signer.Sign(context.Background(), message) require.NoError(t, err) valid, err := pubKey.Verify(message, signature) @@ -248,7 +249,7 @@ func TestKeyPersistence(t *testing.T) { // Sign a test message message := []byte("Test message") - signature, err := signer1.Sign(message) + signature, err := signer1.Sign(context.Background(), message) require.NoError(t, err) // Verify signature works with this key @@ -405,7 +406,7 @@ func TestConcurrentAccess(t *testing.T) { } // Sign message - signature, err := signer.Sign(message) + signature, err := signer.Sign(context.Background(), message) if err != nil { errChan <- err continue @@ -604,7 +605,7 @@ func TestSign_NilPrivateKey(t *testing.T) { } message := []byte("test message") - _, err := signer.Sign(message) + _, err := signer.Sign(context.Background(), message) assert.Error(t, err) assert.Contains(t, err.Error(), "private key not loaded") } diff --git a/pkg/signer/file/local.go b/pkg/signer/file/local.go index 8ab437ae97..4bc602d3be 100644 --- a/pkg/signer/file/local.go +++ b/pkg/signer/file/local.go @@ -1,6 +1,7 @@ package file import ( + "context" "crypto/aes" "crypto/cipher" "crypto/rand" @@ -382,7 +383,7 @@ func (s *FileSystemSigner) loadKeys(passphrase []byte) error { } // Sign signs a message using the private key -func (s *FileSystemSigner) Sign(message []byte) ([]byte, error) { +func (s *FileSystemSigner) Sign(_ context.Context, message []byte) ([]byte, error) { s.mu.RLock() defer s.mu.RUnlock() diff --git a/pkg/signer/noop/signer.go b/pkg/signer/noop/signer.go index ea65c44ecb..b1e239f174 100644 --- a/pkg/signer/noop/signer.go +++ b/pkg/signer/noop/signer.go @@ -1,6 +1,7 @@ package noop import ( + "context" "crypto/sha256" "fmt" @@ -34,7 +35,7 @@ func NewNoopSigner(privKey crypto.PrivKey) (signer.Signer, error) { } // Sign implements the Signer interface by signing the message with the Ed25519 private key. -func (n *NoopSigner) Sign(message []byte) ([]byte, error) { +func (n *NoopSigner) Sign(_ context.Context, message []byte) ([]byte, error) { if n.privKey == nil { return nil, fmt.Errorf("private key not loaded") } diff --git a/pkg/signer/noop/signer_test.go b/pkg/signer/noop/signer_test.go index a3ec506ba5..268f717e61 100644 --- a/pkg/signer/noop/signer_test.go +++ b/pkg/signer/noop/signer_test.go @@ -1,6 +1,7 @@ package noop import ( + "context" "testing" "github.com/libp2p/go-libp2p/core/crypto" @@ -34,7 +35,7 @@ func TestNoopSigner(t *testing.T) { require.NoError(t, err) message := []byte("test message") - signature, err := signer.Sign(message) + signature, err := signer.Sign(context.Background(), message) require.NoError(t, err) require.NotNil(t, signature) @@ -82,7 +83,7 @@ func TestNoopSigner(t *testing.T) { message := []byte("test message") wrongMessage := []byte("wrong message") - signature, err := signer.Sign(message) + signature, err := signer.Sign(context.Background(), message) require.NoError(t, err) pubKey, err := signer.GetPublic() diff --git a/pkg/signer/signer.go b/pkg/signer/signer.go index 09c6b951b4..2c6eb36119 100644 --- a/pkg/signer/signer.go +++ b/pkg/signer/signer.go @@ -1,13 +1,15 @@ package signer import ( + "context" + "github.com/libp2p/go-libp2p/core/crypto" ) // Signer is an interface for signing and verifying messages. type Signer interface { // Sign takes a message as bytes and returns its signature. - Sign(message []byte) ([]byte, error) + Sign(ctx context.Context, message []byte) ([]byte, error) // GetPublic returns the public key paired with this private key. GetPublic() (crypto.PubKey, error) @@ -15,3 +17,4 @@ type Signer interface { // GetAddress returns the address of the signer. GetAddress() ([]byte, error) } + diff --git a/pkg/sync/sync_service_test.go b/pkg/sync/sync_service_test.go index 593b95199a..d817c49370 100644 --- a/pkg/sync/sync_service_test.go +++ b/pkg/sync/sync_service_test.go @@ -372,7 +372,7 @@ func nextHeader(t *testing.T, previousHeader *types.SignedHeader, chainID string } b, err := newSignedHeader.Header.MarshalBinary() require.NoError(t, err) - signature, err := noopSigner.Sign(b) + signature, err := noopSigner.Sign(context.Background(), b) require.NoError(t, err) newSignedHeader.Signature = signature require.NoError(t, newSignedHeader.Validate()) diff --git a/types/utils.go b/types/utils.go index d8c2527521..aa88e96e14 100644 --- a/types/utils.go +++ b/types/utils.go @@ -1,6 +1,7 @@ package types import ( + "context" cryptoRand "crypto/rand" "math/rand" "time" @@ -178,7 +179,7 @@ func GetRandomSignedHeaderCustom(config *HeaderConfig, chainID string) (*SignedH if err != nil { return nil, err } - signature, err := config.Signer.Sign(b) + signature, err := config.Signer.Sign(context.Background(), b) if err != nil { return nil, err } @@ -288,7 +289,7 @@ func GetSignature(header Header, signer signer.Signer) (Signature, error) { if err != nil { return nil, err } - return signer.Sign(b) + return signer.Sign(context.Background(), b) } func getBlockDataWith(nTxs int) *Data { From 25f1aa44ca535c32f9d979ff7acda082e935c781 Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Mon, 16 Mar 2026 18:29:25 +0100 Subject: [PATCH 3/3] Improve kms --- apps/evm/cmd/init.go | 2 +- apps/evm/go.mod | 15 ++++ apps/evm/go.sum | 30 +++++++ apps/grpc/cmd/init.go | 2 +- apps/grpc/go.mod | 15 ++++ apps/grpc/go.sum | 30 +++++++ apps/testapp/cmd/init.go | 2 +- apps/testapp/go.mod | 15 ++++ apps/testapp/go.sum | 30 +++++++ .../da_submitter_integration_test.go | 4 +- .../internal/submitting/da_submitter_test.go | 6 +- block/internal/submitting/submitter_test.go | 4 +- .../syncing/da_retriever_strict_test.go | 7 +- block/internal/syncing/da_retriever_test.go | 2 +- block/internal/syncing/p2p_handler_test.go | 16 ++-- block/internal/syncing/syncer_test.go | 2 +- go.mod | 8 +- pkg/cmd/init.go | 6 +- pkg/cmd/init_test.go | 14 +-- pkg/cmd/run_node.go | 5 +- pkg/config/config.go | 4 +- pkg/config/defaults.go | 16 ++-- pkg/signer/aws/signer.go | 75 +++++++++++----- pkg/signer/aws/signer_test.go | 86 ++++++++++++++++++- pkg/signer/factory/factory.go | 30 ++++--- pkg/signer/file/example_test.go | 2 +- pkg/signer/file/file_signer_test.go | 11 ++- pkg/signer/file/local.go | 5 +- pkg/signer/noop/signer.go | 5 +- pkg/signer/noop/signer_test.go | 5 +- pkg/signer/signer.go | 1 - pkg/store/store_adapter_test.go | 2 +- pkg/sync/sync_service_test.go | 10 +-- types/signed_header_test.go | 7 +- types/utils.go | 20 ++--- types/utils_test.go | 3 +- 36 files changed, 377 insertions(+), 120 deletions(-) diff --git a/apps/evm/cmd/init.go b/apps/evm/cmd/init.go index 3a282a0b24..5c8adbd75c 100644 --- a/apps/evm/cmd/init.go +++ b/apps/evm/cmd/init.go @@ -58,7 +58,7 @@ func InitCmd() *cobra.Command { } } - proposerAddress, err := rollcmd.CreateSigner(&cfg, homePath, passphrase) + proposerAddress, err := rollcmd.CreateSigner(cmd.Context(), &cfg, homePath, passphrase) if err != nil { return err } diff --git a/apps/evm/go.mod b/apps/evm/go.mod index 3bda4fd8a4..ca883eb6c0 100644 --- a/apps/evm/go.mod +++ b/apps/evm/go.mod @@ -25,6 +25,21 @@ require ( github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 // indirect github.com/StackExchange/wmi v1.2.1 // indirect github.com/armon/go-metrics v0.4.1 // indirect + github.com/aws/aws-sdk-go-v2 v1.41.4 // indirect + github.com/aws/aws-sdk-go-v2/config v1.32.12 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 // indirect + github.com/aws/aws-sdk-go-v2/service/kms v1.50.3 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect + github.com/aws/smithy-go v1.24.2 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.20.0 // indirect diff --git a/apps/evm/go.sum b/apps/evm/go.sum index 679352a745..d875f7bcb9 100644 --- a/apps/evm/go.sum +++ b/apps/evm/go.sum @@ -271,6 +271,36 @@ github.com/apache/arrow/go/v14 v14.0.2/go.mod h1:u3fgh3EdgN/YQ8cVQRguVW3R+seMybF github.com/apache/thrift v0.17.0/go.mod h1:OLxhMRJxomX+1I/KUw03qoV3mMz16BwaKI+d4fPBx7Q= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/aws/aws-sdk-go-v2 v1.41.4 h1:10f50G7WyU02T56ox1wWXq+zTX9I1zxG46HYuG1hH/k= +github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= +github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0= +github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g= +github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8= +github.com/aws/aws-sdk-go-v2/credentials v1.19.12/go.mod h1:U3R1RtSHx6NB0DvEQFGyf/0sbrpJrluENHdPy1j/3TE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 h1:zOgq3uezl5nznfoK3ODuqbhVg1JzAGDUhXOsU0IDCAo= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20/go.mod h1:z/MVwUARehy6GAg/yQ1GO2IMl0k++cu1ohP9zo887wE= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 h1:CNXO7mvgThFGqOFgbNAP2nol2qAWBOGfqR/7tQlvLmc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20/go.mod h1:oydPDJKcfMhgfcgBUZaG+toBbwy8yPWubJXBVERtI4o= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 h1:tN6W/hg+pkM+tf9XDkWUbDEjGLb+raoBMFsTodcoYKw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20/go.mod h1:YJ898MhD067hSHA6xYCx5ts/jEd8BSOLtQDL3iZsvbc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 h1:2HvVAIq+YqgGotK6EkMf+KIEqTISmTYh5zLpYyeTo1Y= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20/go.mod h1:V4X406Y666khGa8ghKmphma/7C0DAtEQYhkq9z4vpbk= +github.com/aws/aws-sdk-go-v2/service/kms v1.50.3 h1:s/zDSG/a/Su9aX+v0Ld9cimUCdkr5FWPmBV8owaEbZY= +github.com/aws/aws-sdk-go-v2/service/kms v1.50.3/go.mod h1:/iSgiUor15ZuxFGQSTf3lA2FmKxFsQoc2tADOarQBSw= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 h1:0GFOLzEbOyZABS3PhYfBIx2rNBACYcKty+XGkTgw1ow= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.8/go.mod h1:LXypKvk85AROkKhOG6/YEcHFPoX+prKTowKnVdcaIxE= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 h1:kiIDLZ005EcKomYYITtfsjn7dtOwHDOFy7IbPXKek2o= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.13/go.mod h1:2h/xGEowcW/g38g06g3KpRWDlT+OTfxxI0o1KqayAB8= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 h1:jzKAXIlhZhJbnYwHbvUQZEB8KfgAEuG0dc08Bkda7NU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17/go.mod h1:Al9fFsXjv4KfbzQHGe6V4NZSZQXecFcvaIF4e70FoRA= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 h1:Cng+OOwCHmFljXIxpEVXAGMnBia8MSU6Ch5i9PgBkcU= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.9/go.mod h1:LrlIndBDdjA/EeXeyNBle+gyCwTlizzW5ycgWnvIxkk= +github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= +github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= diff --git a/apps/grpc/cmd/init.go b/apps/grpc/cmd/init.go index 2800003e20..f48114be8c 100644 --- a/apps/grpc/cmd/init.go +++ b/apps/grpc/cmd/init.go @@ -60,7 +60,7 @@ This will create the necessary configuration structure in the specified root dir } } - proposerAddress, err := rollcmd.CreateSigner(&cfg, homePath, passphrase) + proposerAddress, err := rollcmd.CreateSigner(cmd.Context(), &cfg, homePath, passphrase) if err != nil { return err } diff --git a/apps/grpc/go.mod b/apps/grpc/go.mod index 98907d4f52..f89fdc6110 100644 --- a/apps/grpc/go.mod +++ b/apps/grpc/go.mod @@ -20,6 +20,21 @@ require ( connectrpc.com/connect v1.19.1 // indirect connectrpc.com/grpcreflect v1.3.0 // indirect github.com/armon/go-metrics v0.4.1 // indirect + github.com/aws/aws-sdk-go-v2 v1.41.4 // indirect + github.com/aws/aws-sdk-go-v2/config v1.32.12 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 // indirect + github.com/aws/aws-sdk-go-v2/service/kms v1.50.3 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect + github.com/aws/smithy-go v1.24.2 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/boltdb/bolt v1.3.1 // indirect diff --git a/apps/grpc/go.sum b/apps/grpc/go.sum index 626f4a9bc8..e9e6ce6e7f 100644 --- a/apps/grpc/go.sum +++ b/apps/grpc/go.sum @@ -261,6 +261,36 @@ github.com/apache/arrow/go/v14 v14.0.2/go.mod h1:u3fgh3EdgN/YQ8cVQRguVW3R+seMybF github.com/apache/thrift v0.17.0/go.mod h1:OLxhMRJxomX+1I/KUw03qoV3mMz16BwaKI+d4fPBx7Q= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/aws/aws-sdk-go-v2 v1.41.4 h1:10f50G7WyU02T56ox1wWXq+zTX9I1zxG46HYuG1hH/k= +github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= +github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0= +github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g= +github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8= +github.com/aws/aws-sdk-go-v2/credentials v1.19.12/go.mod h1:U3R1RtSHx6NB0DvEQFGyf/0sbrpJrluENHdPy1j/3TE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 h1:zOgq3uezl5nznfoK3ODuqbhVg1JzAGDUhXOsU0IDCAo= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20/go.mod h1:z/MVwUARehy6GAg/yQ1GO2IMl0k++cu1ohP9zo887wE= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 h1:CNXO7mvgThFGqOFgbNAP2nol2qAWBOGfqR/7tQlvLmc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20/go.mod h1:oydPDJKcfMhgfcgBUZaG+toBbwy8yPWubJXBVERtI4o= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 h1:tN6W/hg+pkM+tf9XDkWUbDEjGLb+raoBMFsTodcoYKw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20/go.mod h1:YJ898MhD067hSHA6xYCx5ts/jEd8BSOLtQDL3iZsvbc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 h1:2HvVAIq+YqgGotK6EkMf+KIEqTISmTYh5zLpYyeTo1Y= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20/go.mod h1:V4X406Y666khGa8ghKmphma/7C0DAtEQYhkq9z4vpbk= +github.com/aws/aws-sdk-go-v2/service/kms v1.50.3 h1:s/zDSG/a/Su9aX+v0Ld9cimUCdkr5FWPmBV8owaEbZY= +github.com/aws/aws-sdk-go-v2/service/kms v1.50.3/go.mod h1:/iSgiUor15ZuxFGQSTf3lA2FmKxFsQoc2tADOarQBSw= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 h1:0GFOLzEbOyZABS3PhYfBIx2rNBACYcKty+XGkTgw1ow= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.8/go.mod h1:LXypKvk85AROkKhOG6/YEcHFPoX+prKTowKnVdcaIxE= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 h1:kiIDLZ005EcKomYYITtfsjn7dtOwHDOFy7IbPXKek2o= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.13/go.mod h1:2h/xGEowcW/g38g06g3KpRWDlT+OTfxxI0o1KqayAB8= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 h1:jzKAXIlhZhJbnYwHbvUQZEB8KfgAEuG0dc08Bkda7NU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17/go.mod h1:Al9fFsXjv4KfbzQHGe6V4NZSZQXecFcvaIF4e70FoRA= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 h1:Cng+OOwCHmFljXIxpEVXAGMnBia8MSU6Ch5i9PgBkcU= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.9/go.mod h1:LrlIndBDdjA/EeXeyNBle+gyCwTlizzW5ycgWnvIxkk= +github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= +github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= diff --git a/apps/testapp/cmd/init.go b/apps/testapp/cmd/init.go index 8a046675d1..a6fd34ea73 100644 --- a/apps/testapp/cmd/init.go +++ b/apps/testapp/cmd/init.go @@ -58,7 +58,7 @@ func InitCmd() *cobra.Command { } } - proposerAddress, err := rollcmd.CreateSigner(&cfg, homePath, passphrase) + proposerAddress, err := rollcmd.CreateSigner(cmd.Context(), &cfg, homePath, passphrase) if err != nil { return err } diff --git a/apps/testapp/go.mod b/apps/testapp/go.mod index 289f97a580..43311766bf 100644 --- a/apps/testapp/go.mod +++ b/apps/testapp/go.mod @@ -17,6 +17,21 @@ require ( connectrpc.com/connect v1.19.1 // indirect connectrpc.com/grpcreflect v1.3.0 // indirect github.com/armon/go-metrics v0.4.1 // indirect + github.com/aws/aws-sdk-go-v2 v1.41.4 // indirect + github.com/aws/aws-sdk-go-v2/config v1.32.12 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 // indirect + github.com/aws/aws-sdk-go-v2/service/kms v1.50.3 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect + github.com/aws/smithy-go v1.24.2 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/boltdb/bolt v1.3.1 // indirect diff --git a/apps/testapp/go.sum b/apps/testapp/go.sum index 626f4a9bc8..e9e6ce6e7f 100644 --- a/apps/testapp/go.sum +++ b/apps/testapp/go.sum @@ -261,6 +261,36 @@ github.com/apache/arrow/go/v14 v14.0.2/go.mod h1:u3fgh3EdgN/YQ8cVQRguVW3R+seMybF github.com/apache/thrift v0.17.0/go.mod h1:OLxhMRJxomX+1I/KUw03qoV3mMz16BwaKI+d4fPBx7Q= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/aws/aws-sdk-go-v2 v1.41.4 h1:10f50G7WyU02T56ox1wWXq+zTX9I1zxG46HYuG1hH/k= +github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= +github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0= +github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g= +github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8= +github.com/aws/aws-sdk-go-v2/credentials v1.19.12/go.mod h1:U3R1RtSHx6NB0DvEQFGyf/0sbrpJrluENHdPy1j/3TE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 h1:zOgq3uezl5nznfoK3ODuqbhVg1JzAGDUhXOsU0IDCAo= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20/go.mod h1:z/MVwUARehy6GAg/yQ1GO2IMl0k++cu1ohP9zo887wE= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 h1:CNXO7mvgThFGqOFgbNAP2nol2qAWBOGfqR/7tQlvLmc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20/go.mod h1:oydPDJKcfMhgfcgBUZaG+toBbwy8yPWubJXBVERtI4o= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 h1:tN6W/hg+pkM+tf9XDkWUbDEjGLb+raoBMFsTodcoYKw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20/go.mod h1:YJ898MhD067hSHA6xYCx5ts/jEd8BSOLtQDL3iZsvbc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 h1:2HvVAIq+YqgGotK6EkMf+KIEqTISmTYh5zLpYyeTo1Y= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20/go.mod h1:V4X406Y666khGa8ghKmphma/7C0DAtEQYhkq9z4vpbk= +github.com/aws/aws-sdk-go-v2/service/kms v1.50.3 h1:s/zDSG/a/Su9aX+v0Ld9cimUCdkr5FWPmBV8owaEbZY= +github.com/aws/aws-sdk-go-v2/service/kms v1.50.3/go.mod h1:/iSgiUor15ZuxFGQSTf3lA2FmKxFsQoc2tADOarQBSw= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 h1:0GFOLzEbOyZABS3PhYfBIx2rNBACYcKty+XGkTgw1ow= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.8/go.mod h1:LXypKvk85AROkKhOG6/YEcHFPoX+prKTowKnVdcaIxE= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 h1:kiIDLZ005EcKomYYITtfsjn7dtOwHDOFy7IbPXKek2o= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.13/go.mod h1:2h/xGEowcW/g38g06g3KpRWDlT+OTfxxI0o1KqayAB8= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 h1:jzKAXIlhZhJbnYwHbvUQZEB8KfgAEuG0dc08Bkda7NU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17/go.mod h1:Al9fFsXjv4KfbzQHGe6V4NZSZQXecFcvaIF4e70FoRA= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 h1:Cng+OOwCHmFljXIxpEVXAGMnBia8MSU6Ch5i9PgBkcU= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.9/go.mod h1:LrlIndBDdjA/EeXeyNBle+gyCwTlizzW5ycgWnvIxkk= +github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= +github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= diff --git a/block/internal/submitting/da_submitter_integration_test.go b/block/internal/submitting/da_submitter_integration_test.go index fafe4f044c..b2c4efcd20 100644 --- a/block/internal/submitting/da_submitter_integration_test.go +++ b/block/internal/submitting/da_submitter_integration_test.go @@ -53,7 +53,7 @@ func TestDASubmitter_SubmitHeadersAndData_MarksInclusionAndUpdatesLastSubmitted( hdr1 := &types.SignedHeader{Header: types.Header{BaseHeader: types.BaseHeader{ChainID: gen.ChainID, Height: 1, Time: uint64(time.Now().UnixNano())}, AppHash: stateRoot, ProposerAddress: addr}, Signer: types.Signer{PubKey: pub, Address: addr}} bz1, err := types.DefaultAggregatorNodeSignatureBytesProvider(&hdr1.Header) require.NoError(t, err) - sig1, err := n.Sign(context.Background(), bz1) + sig1, err := n.Sign(t.Context(), bz1) require.NoError(t, err) hdr1.Signature = sig1 data1 := &types.Data{Metadata: &types.Metadata{ChainID: gen.ChainID, Height: 1, Time: uint64(time.Now().UnixNano())}, Txs: types.Txs{types.Tx("a")}} @@ -61,7 +61,7 @@ func TestDASubmitter_SubmitHeadersAndData_MarksInclusionAndUpdatesLastSubmitted( hdr2 := &types.SignedHeader{Header: types.Header{BaseHeader: types.BaseHeader{ChainID: gen.ChainID, Height: 2, Time: uint64(time.Now().Add(time.Second).UnixNano())}, AppHash: stateRoot, ProposerAddress: addr}, Signer: types.Signer{PubKey: pub, Address: addr}} bz2, err := types.DefaultAggregatorNodeSignatureBytesProvider(&hdr2.Header) require.NoError(t, err) - sig2, err := n.Sign(context.Background(), bz2) + sig2, err := n.Sign(t.Context(), bz2) require.NoError(t, err) hdr2.Signature = sig2 data2 := &types.Data{Metadata: &types.Metadata{ChainID: gen.ChainID, Height: 2, Time: uint64(time.Now().Add(time.Second).UnixNano())}, Txs: types.Txs{types.Tx("b")}} diff --git a/block/internal/submitting/da_submitter_test.go b/block/internal/submitting/da_submitter_test.go index defe7e4d73..d25786018b 100644 --- a/block/internal/submitting/da_submitter_test.go +++ b/block/internal/submitting/da_submitter_test.go @@ -175,7 +175,7 @@ func TestDASubmitter_SubmitHeaders_Success(t *testing.T) { for _, header := range []*types.SignedHeader{header1, header2} { bz, err := types.DefaultAggregatorNodeSignatureBytesProvider(&header.Header) require.NoError(t, err) - sig, err := signer.Sign(context.Background(), bz) + sig, err := signer.Sign(t.Context(), bz) require.NoError(t, err) header.Signature = sig } @@ -503,7 +503,7 @@ func TestDASubmitter_SignData(t *testing.T) { } // Create signed data - resultData, resultDataBz, err := submitter.signData(dataList, dataListBz, signer, gen) + resultData, resultDataBz, err := submitter.signData(t.Context(), dataList, dataListBz, signer, gen) require.NoError(t, err) // Should have 2 items (empty data skipped) @@ -542,7 +542,7 @@ func TestDASubmitter_SignData_NilSigner(t *testing.T) { } // Create signed data with nil signer - should fail - _, _, err := submitter.signData(dataList, dataListBz, nil, gen) + _, _, err := submitter.signData(t.Context(), dataList, dataListBz, nil, gen) require.Error(t, err) assert.Contains(t, err.Error(), "signer is nil") } diff --git a/block/internal/submitting/submitter_test.go b/block/internal/submitting/submitter_test.go index d19e9d3eb4..946fba0ab6 100644 --- a/block/internal/submitting/submitter_test.go +++ b/block/internal/submitting/submitter_test.go @@ -442,7 +442,9 @@ func (f *fakeDASubmitter) SubmitData(ctx context.Context, _ []*types.SignedData, // fakeSigner implements signer.Signer with deterministic behavior for tests. type fakeSigner struct{} -func (f *fakeSigner) Sign(msg []byte) ([]byte, error) { return append([]byte(nil), msg...), nil } +func (f *fakeSigner) Sign(_ context.Context, msg []byte) ([]byte, error) { + return append([]byte(nil), msg...), nil +} func (f *fakeSigner) GetPublic() (crypto.PubKey, error) { return nil, nil } func (f *fakeSigner) GetAddress() ([]byte, error) { return []byte("addr"), nil } diff --git a/block/internal/syncing/da_retriever_strict_test.go b/block/internal/syncing/da_retriever_strict_test.go index 4760463577..981a40614e 100644 --- a/block/internal/syncing/da_retriever_strict_test.go +++ b/block/internal/syncing/da_retriever_strict_test.go @@ -1,7 +1,6 @@ package syncing import ( - "context" "testing" "time" @@ -32,7 +31,7 @@ func TestDARetriever_StrictEnvelopeMode_Switch(t *testing.T) { // Sign it bz, err := types.DefaultAggregatorNodeSignatureBytesProvider(&legacyHeader.Header) require.NoError(t, err) - sig, err := signer.Sign(context.Background(), bz) + sig, err := signer.Sign(t.Context(), bz) require.NoError(t, err) legacyHeader.Signature = sig @@ -51,7 +50,7 @@ func TestDARetriever_StrictEnvelopeMode_Switch(t *testing.T) { // Sign content bz2, err := types.DefaultAggregatorNodeSignatureBytesProvider(&envelopeHeader.Header) require.NoError(t, err) - sig2, err := signer.Sign(context.Background(), bz2) + sig2, err := signer.Sign(t.Context(), bz2) require.NoError(t, err) envelopeHeader.Signature = sig2 @@ -62,7 +61,7 @@ func TestDARetriever_StrictEnvelopeMode_Switch(t *testing.T) { contentBytes, err := envelopeHeader.MarshalBinary() require.NoError(t, err) // Sign envelope - envSig, err := signer.Sign(context.Background(), contentBytes) + envSig, err := signer.Sign(t.Context(), contentBytes) require.NoError(t, err) // Marshal to envelope envelopeBlob, err := envelopeHeader.MarshalDAEnvelope(envSig) diff --git a/block/internal/syncing/da_retriever_test.go b/block/internal/syncing/da_retriever_test.go index a6922a3739..1daafe40ec 100644 --- a/block/internal/syncing/da_retriever_test.go +++ b/block/internal/syncing/da_retriever_test.go @@ -71,7 +71,7 @@ func makeSignedDataBytesWithTime(t *testing.T, chainID string, height uint64, pr // For DA SignedData, sign the Data payload bytes (matches DA submission logic) payload, _ := d.MarshalBinary() - sig, err := signer.Sign(context.Background(), payload) + sig, err := signer.Sign(t.Context(), payload) require.NoError(t, err) sd := &types.SignedData{Data: *d, Signature: sig, Signer: types.Signer{PubKey: pub, Address: proposer}} bin, err := sd.MarshalBinary() diff --git a/block/internal/syncing/p2p_handler_test.go b/block/internal/syncing/p2p_handler_test.go index 879dc089ee..8bffc31ede 100644 --- a/block/internal/syncing/p2p_handler_test.go +++ b/block/internal/syncing/p2p_handler_test.go @@ -51,7 +51,7 @@ func p2pMakeSignedHeader(t *testing.T, chainID string, height uint64, proposer [ } bz, err := types.DefaultAggregatorNodeSignatureBytesProvider(&hdr.Header) require.NoError(t, err, "failed to get signature bytes for header") - sig, err := signer.Sign(context.Background(), bz) + sig, err := signer.Sign(t.Context(), bz) require.NoError(t, err, "failed to sign header bytes") hdr.Signature = sig return &types.P2PSignedHeader{SignedHeader: hdr} @@ -140,7 +140,7 @@ func TestP2PHandler_ProcessHeight_EmitsEventWhenHeaderAndDataPresent(t *testing. header.DataHash = data.DACommitment() bz, err := types.DefaultAggregatorNodeSignatureBytesProvider(&header.Header) require.NoError(t, err) - sig, err := p.Signer.Sign(context.Background(), bz) + sig, err := p.Signer.Sign(t.Context(), bz) require.NoError(t, err) header.Signature = sig @@ -159,14 +159,14 @@ func TestP2PHandler_ProcessHeight_EmitsEventWhenHeaderAndDataPresent(t *testing. func TestP2PHandler_ProcessHeight_SkipsWhenDataMissing(t *testing.T) { p := setupP2P(t) - ctx := context.Background() + ctx := t.Context() header := p2pMakeSignedHeader(t, p.Genesis.ChainID, 7, p.ProposerAddr, p.ProposerPub, p.Signer) data := &types.P2PData{Data: makeData(p.Genesis.ChainID, 7, 1)} header.DataHash = data.DACommitment() bz, err := types.DefaultAggregatorNodeSignatureBytesProvider(&header.Header) require.NoError(t, err) - sig, err := p.Signer.Sign(context.Background(), bz) + sig, err := p.Signer.Sign(ctx, bz) require.NoError(t, err) header.Signature = sig @@ -217,7 +217,7 @@ func TestP2PHandler_ProcessHeight_SkipsOnProposerMismatch(t *testing.T) { func TestP2PHandler_ProcessedHeightSkipsPreviouslyHandledBlocks(t *testing.T) { p := setupP2P(t) - ctx := context.Background() + ctx := t.Context() // Mark up to height 5 as processed. p.Handler.SetProcessedHeight(5) @@ -236,7 +236,7 @@ func TestP2PHandler_ProcessedHeightSkipsPreviouslyHandledBlocks(t *testing.T) { header.DataHash = data.DACommitment() bz, err := types.DefaultAggregatorNodeSignatureBytesProvider(&header.Header) require.NoError(t, err) - sig, err := p.Signer.Sign(context.Background(), bz) + sig, err := p.Signer.Sign(ctx, bz) require.NoError(t, err) header.Signature = sig @@ -252,14 +252,14 @@ func TestP2PHandler_ProcessedHeightSkipsPreviouslyHandledBlocks(t *testing.T) { func TestP2PHandler_SetProcessedHeightPreventsDuplicates(t *testing.T) { p := setupP2P(t) - ctx := context.Background() + ctx := t.Context() header := p2pMakeSignedHeader(t, p.Genesis.ChainID, 8, p.ProposerAddr, p.ProposerPub, p.Signer) data := &types.P2PData{Data: makeData(p.Genesis.ChainID, 8, 0)} header.DataHash = data.DACommitment() bz, err := types.DefaultAggregatorNodeSignatureBytesProvider(&header.Header) require.NoError(t, err) - sig, err := p.Signer.Sign(context.Background(), bz) + sig, err := p.Signer.Sign(ctx, bz) require.NoError(t, err) header.Signature = sig diff --git a/block/internal/syncing/syncer_test.go b/block/internal/syncing/syncer_test.go index c42024a4b7..c35efda25b 100644 --- a/block/internal/syncing/syncer_test.go +++ b/block/internal/syncing/syncer_test.go @@ -78,7 +78,7 @@ func makeSignedHeaderBytes( } bz, err := types.DefaultAggregatorNodeSignatureBytesProvider(&hdr.Header) require.NoError(tb, err) - sig, err := signer.Sign(context.Background(), bz) + sig, err := signer.Sign(tb.Context(), bz) require.NoError(tb, err) hdr.Signature = sig bin, err := hdr.MarshalBinary() diff --git a/go.mod b/go.mod index bc25b70e03..bf2431bee7 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,10 @@ retract v0.12.0 // Published by accident require ( connectrpc.com/connect v1.19.1 connectrpc.com/grpcreflect v1.3.0 + github.com/aws/aws-sdk-go-v2 v1.41.4 + github.com/aws/aws-sdk-go-v2/config v1.32.12 + github.com/aws/aws-sdk-go-v2/service/kms v1.50.3 + github.com/aws/smithy-go v1.24.2 github.com/celestiaorg/go-header v0.8.1 github.com/celestiaorg/go-square/merkle v0.0.0-20240627094109-7d01436067a3 github.com/celestiaorg/go-square/v3 v3.0.2 @@ -45,8 +49,6 @@ require ( require ( github.com/armon/go-metrics v0.4.1 // indirect - github.com/aws/aws-sdk-go-v2 v1.41.4 // indirect - github.com/aws/aws-sdk-go-v2/config v1.32.12 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 // indirect @@ -54,12 +56,10 @@ require ( github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 // indirect - github.com/aws/aws-sdk-go-v2/service/kms v1.50.3 // indirect github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect - github.com/aws/smithy-go v1.24.2 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/boltdb/bolt v1.3.1 // indirect diff --git a/pkg/cmd/init.go b/pkg/cmd/init.go index 8a231b8b4d..479684af99 100644 --- a/pkg/cmd/init.go +++ b/pkg/cmd/init.go @@ -3,10 +3,8 @@ package cmd import ( "context" "fmt" - "os" "path/filepath" - rollconf "github.com/evstack/ev-node/pkg/config" rollconf "github.com/evstack/ev-node/pkg/config" "github.com/evstack/ev-node/pkg/hash" "github.com/evstack/ev-node/pkg/p2p/key" @@ -14,7 +12,7 @@ import ( ) // CreateSigner sets up the signer configuration and creates necessary files -func CreateSigner(config *rollconf.Config, homePath string, passphrase string) ([]byte, error) { +func CreateSigner(ctx context.Context, config *rollconf.Config, homePath string, passphrase string) ([]byte, error) { if !config.Node.Aggregator { return nil, nil } @@ -24,7 +22,7 @@ func CreateSigner(config *rollconf.Config, homePath string, passphrase string) ( config.Signer.SignerPath = signerDir } - signer, err := factory.NewSigner(context.Background(), config, passphrase) + signer, err := factory.NewSignerForInit(ctx, config, passphrase) if err != nil { return nil, fmt.Errorf("failed to initialize signer via factory: %w", err) } diff --git a/pkg/cmd/init_test.go b/pkg/cmd/init_test.go index 7bef0c9a41..c3ee9a7c30 100644 --- a/pkg/cmd/init_test.go +++ b/pkg/cmd/init_test.go @@ -24,7 +24,7 @@ func TestCreateSigner(t *testing.T) { Signer: rollconf.SignerConfig{SignerType: "file"}, Node: rollconf.NodeConfig{Aggregator: true}, } - _, err := cmd.CreateSigner(cfg, tmpDir, "") + _, err := cmd.CreateSigner(t.Context(), cfg, tmpDir, "") require.Error(err) assert.Contains(err.Error(), "passphrase is required") }) @@ -36,7 +36,7 @@ func TestCreateSigner(t *testing.T) { Signer: rollconf.SignerConfig{SignerType: "file"}, Node: rollconf.NodeConfig{Aggregator: true}, } - addr, err := cmd.CreateSigner(cfg, tmpDir, "testpass") + addr, err := cmd.CreateSigner(t.Context(), cfg, tmpDir, "testpass") require.NoError(err) assert.NotNil(addr) assert.NotEmpty(addr) @@ -46,16 +46,16 @@ func TestCreateSigner(t *testing.T) { assert.NoError(err, "signer file should exist") }) - // Case 3: Non-File signer, Aggregator -> Error (Remote signer not implemented) + // Case 3: Non-File signer, Aggregator -> Error (unknown signer type) t.Run("RemoteSigner_Aggregator", func(t *testing.T) { tmpDir := t.TempDir() cfg := &rollconf.Config{ Signer: rollconf.SignerConfig{SignerType: "remote"}, Node: rollconf.NodeConfig{Aggregator: true}, } - _, err := cmd.CreateSigner(cfg, tmpDir, "") + _, err := cmd.CreateSigner(t.Context(), cfg, tmpDir, "") require.Error(err) - assert.Contains(err.Error(), "remote signer not implemented") + assert.Contains(err.Error(), "unknown signer type") }) // Case 4: Not Aggregator -> No-op (returns nil, nil) @@ -65,7 +65,7 @@ func TestCreateSigner(t *testing.T) { Signer: rollconf.SignerConfig{SignerType: "file"}, // Signer type doesn't matter here Node: rollconf.NodeConfig{Aggregator: false}, } - addr, err := cmd.CreateSigner(cfg, tmpDir, "testpass") + addr, err := cmd.CreateSigner(t.Context(), cfg, tmpDir, "testpass") require.NoError(err) assert.Nil(addr) }) @@ -83,7 +83,7 @@ func TestCreateSigner(t *testing.T) { Signer: rollconf.SignerConfig{SignerType: "file"}, Node: rollconf.NodeConfig{Aggregator: true}, } - _, err = cmd.CreateSigner(cfg, tmpDir, "testpass") + _, err = cmd.CreateSigner(t.Context(), cfg, tmpDir, "testpass") require.Error(err) assert.Contains(err.Error(), "failed to create signer directory") }) diff --git a/pkg/cmd/run_node.go b/pkg/cmd/run_node.go index fde7485278..f2f49748e4 100644 --- a/pkg/cmd/run_node.go +++ b/pkg/cmd/run_node.go @@ -6,7 +6,6 @@ import ( "fmt" "os" "os/signal" - "path/filepath" "runtime" "strings" "syscall" @@ -136,9 +135,9 @@ func StartNode( var err error signer, err = factory.NewSigner(ctx, &nodeConfig, passphrase) if err != nil { - return fmt.Errorf("failed to initialize signer via factory: %w", err) + return fmt.Errorf("initialize signer via factory: %w", err) } - + if nodeConfig.Signer.SignerType == "awskms" { logger.Info().Msg("initialized AWS KMS signer via factory") } diff --git a/pkg/config/config.go b/pkg/config/config.go index f88e28c6d9..d16f0edbe0 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -304,8 +304,8 @@ type P2PConfig struct { // SignerConfig contains all signer configuration parameters type SignerConfig struct { - SignerType string `mapstructure:"signer_type" yaml:"signer_type" comment:"Type of remote signer to use (file, grpc, awskms)"` - SignerPath string `mapstructure:"signer_path" yaml:"signer_path" comment:"Path to the signer file or address"` + SignerType string `mapstructure:"signer_type" yaml:"signer_type" comment:"Type of remote signer to use (file, awskms)"` + SignerPath string `mapstructure:"signer_path" yaml:"signer_path" comment:"Path to the signer file or address"` KmsKeyID string `mapstructure:"kms_key_id" yaml:"kms_key_id" comment:"AWS KMS Key ID or ARN for awskms signer"` KmsRegion string `mapstructure:"kms_region" yaml:"kms_region" comment:"AWS Region for awskms signer"` KmsProfile string `mapstructure:"kms_profile" yaml:"kms_profile" comment:"AWS Profile for awskms signer"` diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index afde0a67ab..d65aff6520 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -91,14 +91,14 @@ func DefaultConfig() Config { Trace: false, }, Signer: SignerConfig{ - SignerType: "file", - SignerPath: "config", - KmsKeyID: "", - KmsRegion: "", - KmsProfile: "", - KmsTimeout: DurationWrapper{10 * time.Second}, - KmsMaxRetries: 3, - KmsCacheTTL: DurationWrapper{0}, + SignerType: "file", + SignerPath: "config", + KmsKeyID: "", + KmsRegion: "", + KmsProfile: "", + KmsTimeout: DurationWrapper{10 * time.Second}, + KmsMaxRetries: 3, + KmsCacheTTL: DurationWrapper{0}, }, RPC: RPCConfig{ Address: "127.0.0.1:7331", diff --git a/pkg/signer/aws/signer.go b/pkg/signer/aws/signer.go index 22cb9973ca..7622208d85 100644 --- a/pkg/signer/aws/signer.go +++ b/pkg/signer/aws/signer.go @@ -7,7 +7,9 @@ import ( "crypto/ed25519" "crypto/sha256" "crypto/x509" + "errors" "fmt" + "net" "sync" "time" @@ -15,6 +17,7 @@ import ( awsconfig "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/kms" "github.com/aws/aws-sdk-go-v2/service/kms/types" + "github.com/aws/smithy-go" "github.com/libp2p/go-libp2p/core/crypto" "github.com/evstack/ev-node/pkg/signer" @@ -38,26 +41,11 @@ type Options struct { CacheTTL time.Duration } -func (o *Options) timeout() time.Duration { - if o != nil && o.Timeout > 0 { - return o.Timeout - } - return 10 * time.Second -} +func (o *Options) timeout() time.Duration { return o.Timeout } -func (o *Options) maxRetries() int { - if o != nil && o.MaxRetries > 0 { - return o.MaxRetries - } - return 3 -} +func (o *Options) maxRetries() int { return o.MaxRetries } -func (o *Options) cacheTTL() time.Duration { - if o != nil { - return o.CacheTTL - } - return 0 -} +func (o *Options) cacheTTL() time.Duration { return o.CacheTTL } // KmsSigner implements the signer.Signer interface using AWS KMS. type KmsSigner struct { @@ -102,10 +90,19 @@ func NewKmsSignerFromClient(ctx context.Context, client KMSClient, keyID string, if keyID == "" { return nil, fmt.Errorf("aws kms key ID is required") } + if client == nil { + return nil, fmt.Errorf("aws kms client is required") + } - o := Options{} + o := Options{Timeout: 5 * time.Second, MaxRetries: 3, CacheTTL: 0} if opts != nil { - o = *opts + if opts.Timeout > 0 { + o.Timeout = opts.Timeout + } + if opts.MaxRetries >= 0 { + o.MaxRetries = opts.MaxRetries + } + o.CacheTTL = opts.CacheTTL } s := &KmsSigner{ @@ -170,8 +167,9 @@ func (s *KmsSigner) Sign(ctx context.Context, message []byte) ([]byte, error) { var lastErr error maxRetries := s.opts.maxRetries() timeout := s.opts.timeout() + maxAttempts := maxRetries + 1 - for attempt := 0; attempt < maxRetries; attempt++ { + for attempt := 0; attempt < maxAttempts; attempt++ { if attempt > 0 { // Exponential backoff: 100ms, 200ms, 400ms, ... backoff := time.Duration(100<