diff --git a/node/cmd/guardiand/adminclient.go b/node/cmd/guardiand/adminclient.go index ee087431aa..34c9b65eef 100644 --- a/node/cmd/guardiand/adminclient.go +++ b/node/cmd/guardiand/adminclient.go @@ -236,13 +236,15 @@ func getPublicRPCServiceClient(ctx context.Context, addr string) (*grpc.ClientCo } func runSignWormchainValidatorAddress(cmd *cobra.Command, args []string) error { + ctx := context.Background() + guardianSignerUri := args[0] wormchainAddress := args[1] if !strings.HasPrefix(wormchainAddress, "wormhole") || strings.HasPrefix(wormchainAddress, "wormholeval") { return errors.New("must provide a bech32 address that has 'wormhole' prefix") } - guardianSigner, err := guardiansigner.NewGuardianSignerFromUri(guardianSignerUri, *unsafeDevnetMode) + guardianSigner, err := guardiansigner.NewGuardianSignerFromUri(ctx, guardianSignerUri, *unsafeDevnetMode) if err != nil { return fmt.Errorf("failed to create new guardian signer from uri: %w", err) } @@ -254,7 +256,7 @@ func runSignWormchainValidatorAddress(cmd *cobra.Command, args []string) error { // Hash and sign address addrHash := crypto.Keccak256Hash(sdk.SignedWormchainAddressPrefix, addr) - sig, err := guardianSigner.Sign(addrHash.Bytes()) + sig, err := guardianSigner.Sign(ctx, addrHash.Bytes()) if err != nil { return fmt.Errorf("failed to sign wormchain address: %w", err) } diff --git a/node/cmd/guardiand/node.go b/node/cmd/guardiand/node.go index a3ea81a974..058981f341 100644 --- a/node/cmd/guardiand/node.go +++ b/node/cmd/guardiand/node.go @@ -695,14 +695,18 @@ func runNode(cmd *cobra.Command, args []string) { } } + // Node's main lifecycle context. + rootCtx, rootCtxCancel = context.WithCancel(context.Background()) + defer rootCtxCancel() + // Create the Guardian Signer - guardianSigner, err := guardiansigner.NewGuardianSignerFromUri(*guardianSignerUri, env == common.UnsafeDevNet) + guardianSigner, err := guardiansigner.NewGuardianSignerFromUri(rootCtx, *guardianSignerUri, env == common.UnsafeDevNet) if err != nil { logger.Fatal("failed to create a new guardian signer", zap.Error(err)) } - logger.Info("Loaded guardian key", zap.String( - "address", ethcrypto.PubkeyToAddress(guardianSigner.PublicKey()).String())) + logger.Info("Created the guardian signer", zap.String( + "address", ethcrypto.PubkeyToAddress(guardianSigner.PublicKey(rootCtx)).String())) // Load p2p private key var p2pKey libp2p_crypto.PrivKey @@ -758,7 +762,7 @@ func runNode(cmd *cobra.Command, args []string) { labels := map[string]string{ "node_name": *nodeName, "node_key": peerID.String(), - "guardian_addr": ethcrypto.PubkeyToAddress(guardianSigner.PublicKey()).String(), + "guardian_addr": ethcrypto.PubkeyToAddress(guardianSigner.PublicKey(rootCtx)).String(), "network": *p2pNetworkID, "version": version.Version(), } @@ -965,10 +969,6 @@ func runNode(cmd *cobra.Command, args []string) { rpcMap[ibcChain.String()] = "IBC" } - // Node's main lifecycle context. - rootCtx, rootCtxCancel = context.WithCancel(context.Background()) - defer rootCtxCancel() - // Handle SIGTERM sigterm := make(chan os.Signal, 1) signal.Notify(sigterm, syscall.SIGTERM) @@ -1105,7 +1105,7 @@ func runNode(cmd *cobra.Command, args []string) { info.PromRemoteURL = *promRemoteURL info.Labels = map[string]string{ "node_name": *nodeName, - "guardian_addr": ethcrypto.PubkeyToAddress(guardianSigner.PublicKey()).String(), + "guardian_addr": ethcrypto.PubkeyToAddress(guardianSigner.PublicKey(rootCtx)).String(), "network": *p2pNetworkID, "version": version.Version(), "product": "wormhole", diff --git a/node/go.mod b/node/go.mod index 4d2bc8912d..b96045c3df 100644 --- a/node/go.mod +++ b/node/go.mod @@ -46,6 +46,9 @@ require ( require ( github.com/CosmWasm/wasmd v0.30.0 github.com/algorand/go-algorand-sdk v1.23.0 + github.com/aws/aws-sdk-go v1.55.5 + github.com/aws/aws-sdk-go-v2/config v1.28.1 + github.com/aws/aws-sdk-go-v2/service/kms v1.37.3 github.com/benbjohnson/clock v1.3.5 github.com/blendle/zapdriver v1.3.1 github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce @@ -84,7 +87,20 @@ require ( github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect github.com/armon/go-metrics v0.4.0 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect - github.com/aws/aws-sdk-go v1.44.187 // indirect + github.com/aws/aws-sdk-go-v2 v1.32.3 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.42 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.18 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect + github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.42.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3 // indirect + github.com/aws/aws-sdk-go-v2/service/route53 v1.46.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.32.3 // indirect + github.com/aws/smithy-go v1.22.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/btcsuite/btcd v0.22.1 // indirect diff --git a/node/go.sum b/node/go.sum index 82ce15f2a7..a6e4376b68 100644 --- a/node/go.sum +++ b/node/go.sum @@ -692,21 +692,79 @@ github.com/aws/aws-sdk-go v1.36.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2z github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= -github.com/aws/aws-sdk-go v1.44.187 h1:D5CsRomPnlwDHJCanL2mtaLIcbhjiWxNh5j8zvaWdJA= -github.com/aws/aws-sdk-go v1.44.187/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= +github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= +github.com/aws/aws-sdk-go-v2 v1.16.0/go.mod h1:lJYcuZZEHWNIb6ugJjbQY1fykdoobWbOS7kJYb4APoI= +github.com/aws/aws-sdk-go-v2 v1.32.2 h1:AkNLZEyYMLnx/Q/mSKkcMqwNFXMAvFto9bNsHqcTduI= +github.com/aws/aws-sdk-go-v2 v1.32.2/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= +github.com/aws/aws-sdk-go-v2 v1.32.3 h1:T0dRlFBKcdaUPGNtkBSwHZxrtis8CQU17UpNBZYd0wk= +github.com/aws/aws-sdk-go-v2 v1.32.3/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= +github.com/aws/aws-sdk-go-v2/config v1.15.1 h1:hTIZFepYESYyowQUBo47lu69WSxsYqGUILY9Nu8+7pY= +github.com/aws/aws-sdk-go-v2/config v1.15.1/go.mod h1:MZHGbuW2WnqIOQQBKu2ZkhTjuutZSTnn56TDq4QyydE= +github.com/aws/aws-sdk-go-v2/config v1.28.1 h1:oxIvOUXy8x0U3fR//0eq+RdCKimWI900+SV+10xsCBw= +github.com/aws/aws-sdk-go-v2/config v1.28.1/go.mod h1:bRQcttQJiARbd5JZxw6wG0yIK3eLeSCPdg6uqmmlIiI= github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo= +github.com/aws/aws-sdk-go-v2/credentials v1.11.0 h1:gc4Uhs80s60nmLon5Z4JXWinX2BkAGT0YROoUT8h8U4= +github.com/aws/aws-sdk-go-v2/credentials v1.11.0/go.mod h1:EdV1ZFgtZ4XM5RDHWcRWK8H+xW5duNVBqWj2oLu7tRo= +github.com/aws/aws-sdk-go-v2/credentials v1.17.42 h1:sBP0RPjBU4neGpIYyx8mkU2QqLPl5u9cmdTWVzIpHkM= +github.com/aws/aws-sdk-go-v2/credentials v1.17.42/go.mod h1:FwZBfU530dJ26rv9saAbxa9Ej3eF/AK0OAY86k13n4M= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.1 h1:F9Je1nq5YXfMOv6451NHvMf6U0iTWeMnsG0MMIQoUmk= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.1/go.mod h1:Yph0XsTbQ5GGZ2+mO1a03P/SO9fdX3t1nejIp2tq79g= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.18 h1:68jFVtt3NulEzojFesM/WVarlFpCaXLKaBxDpzkQ9OQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.18/go.mod h1:Fjnn5jQVIo6VyedMc0/EhPpfNlPl7dHV916O6B+49aE= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.7/go.mod h1:oB9nZcxH1cGq7NPGurVJwxrO2vmJ9mmEBayCwcAlmT8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 h1:UAsR3xA31QGf79WzpG/ixT9FZvQlh5HY1NRqSHBNOCk= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21/go.mod h1:JNr43NFf5L9YaG3eKTm7HQzls9J+A9YYcGI5Quh1r2Y= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 h1:Jw50LwEkVjuVzE1NzkhNKkBf9cRN7MtE1F/b2cOKTUM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22/go.mod h1:Y/SmAyPcOTmpeVaWSzSKiILfXTVJwrGmYZhcRbhWuEY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.1/go.mod h1:K4vz7lRYCyLYpYAMCLObODahFgARdD3YVa0MvQte9Co= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 h1:6jZVETqmYCadGFvrYEQfC5fAQmlo80CeL5psbno6r0s= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21/go.mod h1:1SR0GbLlnN3QUmYaflZNiH1ql+1qrSiB2vwcJ+4UM60= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 h1:981MHwBaRZM7+9QSR6XamDzF/o7ouUGxFzr+nVSIhrs= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22/go.mod h1:1RA1+aBEfn+CAB/Mh0MB6LsdCYCnjZm7tKXtnk499ZQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.8 h1:adr3PfiggFtqgFofAMUFCtdvwzpf3QxPES4ezK4M3iI= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.8/go.mod h1:wLbQYt36AJqaRZUQiCNXzbtkNigyPfKHrotHuIDiCy8= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.42.3 h1:C6oS3hSFIB1ydz3dhgkZ0HyzWV41qVjNxS/mA0AGLMQ= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.42.3/go.mod h1:OXYzq1k1XwhwghGdHASEDeFr0Ij8dyFRaIy6w0yrIms= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.1 h1:B/SPX7J+Y0Yrcjv60Nhbh1gC2uBN47SfN8JYre6Mp4M= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.1/go.mod h1:2Hhr9Eh1gJzDatwACX/ozAZ/ljq5vzvPRu5cdu25tzc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3 h1:qcxX0JYlgWH3hpPUnd6U0ikcl6LLA9sLkXE2w1fpMvY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3/go.mod h1:cLSNEmI45soc+Ef8K/L+8sEA3A3pYFEYf5B5UI+6bH4= +github.com/aws/aws-sdk-go-v2/service/kms v1.37.2 h1:tfBABi5R6aSZlhgTWHxL+opYUDOnIGoNcJLwVYv0jLM= +github.com/aws/aws-sdk-go-v2/service/kms v1.37.2/go.mod h1:dZYFcQwuoh+cLOlFnZItijZptmyDhRIkOKWFO1CfzV8= +github.com/aws/aws-sdk-go-v2/service/kms v1.37.3 h1:VpyBA6KP6JgzwokQps8ArQPGy9rFej8adwuuQGcduH8= +github.com/aws/aws-sdk-go-v2/service/kms v1.37.3/go.mod h1:TT/9V4PcmSPpd8LPUNJ8hBHJmpqcfhx6MrbWTkvyR+4= github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4= +github.com/aws/aws-sdk-go-v2/service/route53 v1.46.0 h1:AaOWmXBSDSIEsTzx8Y2nYAxckgmBPNiRU5mjn/a9ynI= +github.com/aws/aws-sdk-go-v2/service/route53 v1.46.0/go.mod h1:IN9bx4yLAa3a3J7A41skQefcYObNv6ARAd2i5WxvGKg= github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= +github.com/aws/aws-sdk-go-v2/service/sso v1.11.1 h1:DyHctRsJIAWIvom1Itb4T84D2jwpIu+KIi3d0SFaswg= +github.com/aws/aws-sdk-go-v2/service/sso v1.11.1/go.mod h1:CvFTucADIx7U/M44vjLs/ZttpQHdpxwK+62+dUGhDeY= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.3 h1:UTpsIf0loCIWEbrqdLb+0RxnTXfWh2vhw4nQmFi4nPc= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.3/go.mod h1:FZ9j3PFHHAR+w0BSEjK955w5YD2UwB/l/H0yAK3MJvI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.3 h1:2YCmIXv3tmiItw0LlYf6v7gEHebLY45kBEnPezbUKyU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.3/go.mod h1:u19stRyNPxGhj6dRm+Cdgu6N75qnbW7+QN0q0dsAk58= github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= +github.com/aws/aws-sdk-go-v2/service/sts v1.16.1 h1:xsOtPAvHqhvQvBza5ohaUcfq1LceH2lZKMUGZJKiZiM= +github.com/aws/aws-sdk-go-v2/service/sts v1.16.1/go.mod h1:Aq2/Qggh2oemSfyHH+EO4UBbgWG6zFCXLHYI4ILTY7w= +github.com/aws/aws-sdk-go-v2/service/sts v1.32.3 h1:wVnQ6tigGsRqSWDEEyH6lSAJ9OyFUsSnbaUWChuSGzs= +github.com/aws/aws-sdk-go-v2/service/sts v1.32.3/go.mod h1:VZa9yTFyj4o10YGsmDO4gbQJUvvhY72fhumT8W4LqsE= github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= +github.com/aws/smithy-go v1.11.1/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= +github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM= +github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -3524,7 +3582,6 @@ golang.org/x/net v0.0.0-20220726230323-06994584191e/go.mod h1:AaygXjzTFtRAg2ttMY golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= @@ -3760,7 +3817,6 @@ golang.org/x/sys v0.0.0-20220727055044-e65921a090b8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -3776,7 +3832,6 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= diff --git a/node/hack/accountant/send_obs.go b/node/hack/accountant/send_obs.go index 63bcb8d94c..f64bdbf843 100644 --- a/node/hack/accountant/send_obs.go +++ b/node/hack/accountant/send_obs.go @@ -45,7 +45,7 @@ func main() { ) logger.Info("Initializing guardian signer", zap.String("guardianSignerUri", guardianSignerUri)) - guardianSigner, err := guardiansigner.NewGuardianSignerFromUri(guardianSignerUri, true) + guardianSigner, err := guardiansigner.NewGuardianSignerFromUri(ctx, guardianSignerUri, true) if err != nil { logger.Fatal("failed to load guardian key", zap.Error(err)) diff --git a/node/pkg/accountant/accountant.go b/node/pkg/accountant/accountant.go index b2eb99f97b..9b096cc2e1 100644 --- a/node/pkg/accountant/accountant.go +++ b/node/pkg/accountant/accountant.go @@ -136,7 +136,7 @@ func NewAccountant( enforceFlag: enforceFlag, guardianSigner: guardianSigner, gst: gst, - guardianAddr: ethCrypto.PubkeyToAddress(guardianSigner.PublicKey()), + guardianAddr: ethCrypto.PubkeyToAddress(guardianSigner.PublicKey(ctx)), msgChan: msgChan, tokenBridges: make(validEmitters), pendingTransfers: make(map[string]*pendingEntry), diff --git a/node/pkg/accountant/submit_obs.go b/node/pkg/accountant/submit_obs.go index 2a4658f6f3..513e14a210 100644 --- a/node/pkg/accountant/submit_obs.go +++ b/node/pkg/accountant/submit_obs.go @@ -342,7 +342,7 @@ func SubmitObservationsToContract( return nil, fmt.Errorf("failed to sign accountant Observation request: %w", err) } - sigBytes, err := guardianSigner.Sign(digest.Bytes()) + sigBytes, err := guardianSigner.Sign(ctx, digest.Bytes()) if err != nil { return nil, fmt.Errorf("failed to sign accountant Observation request: %w", err) } diff --git a/node/pkg/adminrpc/adminserver.go b/node/pkg/adminrpc/adminserver.go index 85e2b68b70..2f345b9807 100644 --- a/node/pkg/adminrpc/adminserver.go +++ b/node/pkg/adminrpc/adminserver.go @@ -1163,7 +1163,7 @@ func (s *nodePrivilegedService) SignExistingVAA(ctx context.Context, req *nodev1 } // Add local signature - sig, err := s.guardianSigner.Sign(v.SigningDigest().Bytes()) + sig, err := s.guardianSigner.Sign(ctx, v.SigningDigest().Bytes()) if err != nil { panic(err) } diff --git a/node/pkg/adminrpc/adminserver_test.go b/node/pkg/adminrpc/adminserver_test.go index f5f6ed6e98..82791b832c 100644 --- a/node/pkg/adminrpc/adminserver_test.go +++ b/node/pkg/adminrpc/adminserver_test.go @@ -95,7 +95,7 @@ func generateGuardianSigners(num int) (signers []guardiansigner.GuardianSigner, panic(err) } signers = append(signers, signer) - addrs = append(addrs, ethcrypto.PubkeyToAddress(signer.PublicKey())) + addrs = append(addrs, ethcrypto.PubkeyToAddress(signer.PublicKey(context.Background()))) } return } @@ -122,7 +122,7 @@ func generateMockVAA(gsIndex uint32, signers []guardiansigner.GuardianSigner, t Payload: []byte("test"), } for i, signer := range signers { - sig, err := signer.Sign(v.SigningDigest().Bytes()) + sig, err := signer.Sign(context.Background(), v.SigningDigest().Bytes()) if err != nil { require.NoError(t, err) } @@ -164,7 +164,7 @@ func setupAdminServerForVAASigning(gsIndex uint32, gsAddrs []common.Address) *no governor: nil, evmConnector: connector, guardianSigner: guardianSigner, - guardianAddress: ethcrypto.PubkeyToAddress(guardianSigner.PublicKey()), + guardianAddress: ethcrypto.PubkeyToAddress(guardianSigner.PublicKey(context.Background())), } } diff --git a/node/pkg/governor/governor_monitoring.go b/node/pkg/governor/governor_monitoring.go index fe7118988a..b900565d31 100644 --- a/node/pkg/governor/governor_monitoring.go +++ b/node/pkg/governor/governor_monitoring.go @@ -75,6 +75,7 @@ package governor import ( + "context" "fmt" "sort" "time" @@ -487,7 +488,7 @@ var ( }) ) -func (gov *ChainGovernor) CollectMetrics(hb *gossipv1.Heartbeat, sendC chan<- []byte, guardianSigner guardiansigner.GuardianSigner, ourAddr ethCommon.Address) { +func (gov *ChainGovernor) CollectMetrics(ctx context.Context, hb *gossipv1.Heartbeat, sendC chan<- []byte, guardianSigner guardiansigner.GuardianSigner, ourAddr ethCommon.Address) { gov.mutex.Lock() defer gov.mutex.Unlock() @@ -547,7 +548,7 @@ func (gov *ChainGovernor) CollectMetrics(hb *gossipv1.Heartbeat, sendC chan<- [] metricTotalEnqueuedVAAs.Set(float64(totalPending)) if startTime.After(gov.nextConfigPublishTime) { - gov.publishConfig(hb, sendC, guardianSigner, ourAddr) + gov.publishConfig(ctx, hb, sendC, guardianSigner, ourAddr) gov.nextConfigPublishTime = startTime.Add(time.Minute * time.Duration(5)) } @@ -560,7 +561,7 @@ func (gov *ChainGovernor) CollectMetrics(hb *gossipv1.Heartbeat, sendC chan<- [] var governorMessagePrefixConfig = []byte("governor_config_000000000000000000|") var governorMessagePrefixStatus = []byte("governor_status_000000000000000000|") -func (gov *ChainGovernor) publishConfig(hb *gossipv1.Heartbeat, sendC chan<- []byte, guardianSigner guardiansigner.GuardianSigner, ourAddr ethCommon.Address) { +func (gov *ChainGovernor) publishConfig(ctx context.Context, hb *gossipv1.Heartbeat, sendC chan<- []byte, guardianSigner guardiansigner.GuardianSigner, ourAddr ethCommon.Address) { chains := make([]*gossipv1.ChainGovernorConfig_Chain, 0) // Iterate deterministically by accessing keys from this slice instead of the chainEntry map directly for _, cid := range gov.chainIds { @@ -600,7 +601,7 @@ func (gov *ChainGovernor) publishConfig(hb *gossipv1.Heartbeat, sendC chan<- []b digest := ethCrypto.Keccak256Hash(append(governorMessagePrefixConfig, b...)) - sig, err := guardianSigner.Sign(digest.Bytes()) + sig, err := guardianSigner.Sign(ctx, digest.Bytes()) if err != nil { panic(err) } @@ -685,7 +686,7 @@ func (gov *ChainGovernor) publishStatus(hb *gossipv1.Heartbeat, sendC chan<- []b digest := ethCrypto.Keccak256Hash(append(governorMessagePrefixStatus, b...)) - sig, err := guardianSigner.Sign(digest.Bytes()) + sig, err := guardianSigner.Sign(context.Background(), digest.Bytes()) if err != nil { panic(err) } diff --git a/node/pkg/guardiansigner/amazonkms.go b/node/pkg/guardiansigner/amazonkms.go new file mode 100644 index 0000000000..01f8afb6f3 --- /dev/null +++ b/node/pkg/guardiansigner/amazonkms.go @@ -0,0 +1,250 @@ +package guardiansigner + +import ( + "bytes" + "context" + "crypto/ecdsa" + "encoding/asn1" + "errors" + "fmt" + "math/big" + "strings" + "time" + + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/kms" + kms_types "github.com/aws/aws-sdk-go-v2/service/kms/types" + "github.com/aws/aws-sdk-go/aws" + ethcrypto "github.com/ethereum/go-ethereum/crypto" +) + +var ( + secp256k1N = ethcrypto.S256().Params().N + secp256k1HalfN = new(big.Int).Div(secp256k1N, big.NewInt(2)) + + // The timeout for KMS operations. This is necessary to avoid situations where + // the signing or verification is blocked indefinitely. + KMS_TIMEOUT = time.Second * 15 +) + +// The ASN.1 structure for an ECDSA signature produced by AWS KMS. +type asn1EcSig struct { + R asn1.RawValue + S asn1.RawValue +} + +// The ASN.1 structure for an ECDSA public key produced by AWS KMS. +type asn1EcPublicKey struct { + EcPublicKeyInfo asn1EcPublicKeyInfo + PublicKey asn1.BitString +} + +// The ASN.1 structure for the public key info in an ECDSA public key produced by AWS KMS. +type asn1EcPublicKeyInfo struct { + Algorithm asn1.ObjectIdentifier + Parameters asn1.ObjectIdentifier +} + +// getRegionFromArn extracts the region from an ARN. The region is at index 3 in the ARN. +func getRegionFromArn(arn string) string { + // Information in ARNs are colon-separated + arn_parts := strings.Split(arn, ":") + + // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html#arns-syntax + // The format of an ARN is arn:partition:service:region:account-id:resource-info, so + // the region is at index 3. + if len(arn) < 4 { + return "" + } + + return arn_parts[3] +} + +// AmazonKms is a signer that uses AWS KMS to sign messages. The URI is expected to be +// in the format amazonkms://. +type AmazonKms struct { + keyId string + region string + publicKey ecdsa.PublicKey + client *kms.Client +} + +// NewAmazonKmsSigner creates a new AmazonKms signer. The keyPath is expected to be an ARN, +// identifying the key in AWS KMS. The region is extracted from the ARN, and the AWS KMS +// client is created with the region. +// NOTE: The public key is retrieved during signer creation, and stored as a property of the +// signer. This is because the public key is not expected to change during runtime. +func NewAmazonKmsSigner(ctx context.Context, unsafeDevMode bool, keyPath string) (*AmazonKms, error) { + timeoutCtx, cancel := context.WithTimeout(ctx, KMS_TIMEOUT) + defer cancel() + + // Extract the region from the key path. The region is required to create a new KMS client. + // If the region is not present in the key path, the ARN is considered invalid. + region := getRegionFromArn(keyPath) + + if region == "" { + return nil, errors.New("Invalid KMS ARN") + } + + amazonKmsSigner := AmazonKms{ + keyId: keyPath, + region: getRegionFromArn(keyPath), + } + + // Create a configuration object to create a new KMS client from. The region passed to + // `config.WithDefaultRegion()` must match the region in the actual ARN, otherwise the SDK throws + // an error. This is why the region is first extracted from the keyPath. + cfg, err := config.LoadDefaultConfig(timeoutCtx, config.WithDefaultRegion(amazonKmsSigner.region)) + if err != nil { + return nil, errors.New("Failed to load default config") + } + + amazonKmsSigner.client = kms.NewFromConfig(cfg) + + // Get the public key here, and store it as a property. The public key shouldn't change during + // runtime, so it's safe to fetch once and store it as a property. + pubKeyOutput, err := amazonKmsSigner.client.GetPublicKey(timeoutCtx, &kms.GetPublicKeyInput{ + KeyId: aws.String(amazonKmsSigner.keyId), + }) + + if err != nil { + return nil, fmt.Errorf("KMS signer creation failed: %w", err) + } + + var asn1Pubkey asn1EcPublicKey + _, err = asn1.Unmarshal(pubKeyOutput.PublicKey, &asn1Pubkey) + + if err != nil { + return nil, fmt.Errorf("Failed to unmarshal KMS public key: %w", err) + } + + ecdsaPubkey := ecdsa.PublicKey{ + X: new(big.Int).SetBytes(asn1Pubkey.PublicKey.Bytes[1 : 1+32]), + Y: new(big.Int).SetBytes(asn1Pubkey.PublicKey.Bytes[1+32:]), + } + + amazonKmsSigner.publicKey = ecdsaPubkey + + return &amazonKmsSigner, nil +} + +func (a *AmazonKms) Sign(ctx context.Context, hash []byte) (signature []byte, err error) { + timeoutCtx, cancel := context.WithTimeout(ctx, KMS_TIMEOUT) + defer cancel() + + // Call the AWS KMS service to sign the input hash. + res, err := a.client.Sign(timeoutCtx, &kms.SignInput{ + KeyId: aws.String(a.keyId), + Message: hash, + SigningAlgorithm: kms_types.SigningAlgorithmSpecEcdsaSha256, + MessageType: kms_types.MessageTypeDigest, + }) + + if err != nil { + return nil, fmt.Errorf("KMS Signing failed: %w", err) + } + + // Decode r and s values + r, s, err := derSignatureToRS(res.Signature) + + if err != nil { + return nil, fmt.Errorf("Failed to decode signature: %w", err) + } + + // if s is greater than secp256k1HalfN, we need to substract secp256k1N from it + sBigInt := new(big.Int).SetBytes(s) + if sBigInt.Cmp(secp256k1HalfN) > 0 { + s = new(big.Int).Sub(secp256k1N, sBigInt).Bytes() + } + + // r and s need to be 32 bytes in size + r = adjustBufferSize(r) + s = adjustBufferSize(s) + + // AWS KMS does not provide the recovery id. But that doesn't matter too much, since we can + // attempt recovery id's 0 and 1, and in the process ensure that the signature is valid. + expectedPublicKey := a.PublicKey(ctx) + signature = append(r, s...) + + // try recovery id 0 + ecSigWithRecid := append(signature, []byte{0}...) + pubkey, _ := ethcrypto.SigToPub(hash[:], ecSigWithRecid) + + if bytes.Equal(ethcrypto.CompressPubkey(pubkey), ethcrypto.CompressPubkey(&expectedPublicKey)) { + return ecSigWithRecid, nil + } + + ecSigWithRecid = append(signature, []byte{1}...) + pubkey, _ = ethcrypto.SigToPub(hash[:], ecSigWithRecid) + + // try recovery id 1 + if bytes.Equal(ethcrypto.CompressPubkey(pubkey), ethcrypto.CompressPubkey(&expectedPublicKey)) { + return ecSigWithRecid, nil + } + + // Reaching this return implies that it wasn't possible to generate a valid signature. This shouldn't + // happen, unless there is something seriously wrong with the KMS service. + return nil, fmt.Errorf("Failed to generate valid signature") +} + +func (a *AmazonKms) PublicKey(ctx context.Context) ecdsa.PublicKey { + return a.publicKey +} + +func (a *AmazonKms) Verify(ctx context.Context, sig []byte, hash []byte) (bool, error) { + timeoutCtx, cancel := context.WithTimeout(ctx, time.Second*15) + defer cancel() + + // Use ethcrypto to recover the public key + recoveredPubKey, err := ethcrypto.SigToPub(hash, sig) + + if err != nil { + return false, err + } + + // Load the KMS signer's public key + kmsPublicKey := a.PublicKey(timeoutCtx) + + return recoveredPubKey.Equal(kmsPublicKey), nil +} + +// https://bitcoin.stackexchange.com/questions/92680/what-are-the-der-signature-and-sec-format +// 1. 0x30 byte: header byte to indicate compound structure +// 2. one byte to encode the length of the following data +// 3. 0x02: header byte indicating an integer +// 4. one byte to encode the length of the following r value +// 5. the r value as a big-endian integer +// 6. 0x02: header byte indicating an integer +// 7. one byte to encode the length of the following s value +// 8. the s value as a big-endian integer +func derSignatureToRS(signature []byte) ([]byte, []byte, error) { + var sigAsn1 asn1EcSig + _, err := asn1.Unmarshal(signature, &sigAsn1) + + if err != nil { + return nil, nil, err + } + + return sigAsn1.R.Bytes, sigAsn1.S.Bytes, nil +} + +// adjustBufferSize takes an input buffer and +// a) trims it down to 32 bytes, if the input length is greater than 32, or +// b) returns the input as-is, if the input length is equal to 32, or +// c) left-pads it to 32 bytes, if the input length is less than 32. +func adjustBufferSize(b []byte) []byte { + length := len(b) + + if length == 32 { + return b + } + + if length > 32 { + return b[length-32:] + } + + tmp := make([]byte, 32) + copy(tmp[32-length:], b) + + return tmp +} diff --git a/node/pkg/guardiansigner/benchmarksigner.go b/node/pkg/guardiansigner/benchmarksigner.go new file mode 100644 index 0000000000..14dae8059f --- /dev/null +++ b/node/pkg/guardiansigner/benchmarksigner.go @@ -0,0 +1,78 @@ +package guardiansigner + +/* + The Benchmark signer is a type of signer that wraps other signers, + recording the latency of signing and signature verification into + histograms. As additional signers are implemented, relying on 3rd + party services, benchmarking signers is useful to ensure observation + signing happens at an acceptable rate. +*/ + +import ( + "context" + "crypto/ecdsa" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +// The BenchmarkSigner is a signer that wraps other signers, recording the latency of +// signing and signature verification through prometheus histograms. +type BenchmarkSigner struct { + innerSigner GuardianSigner +} + +var ( + guardianSignerSigningLatency = promauto.NewHistogram( + prometheus.HistogramOpts{ + Name: "wormhole_guardian_signer_signing_latency_us", + Help: "Latency histogram for Guardian signing requests", + Buckets: []float64{10.0, 20.0, 50.0, 100.0, 1000.0, 5000.0, 10000.0, 100_000.0, 1_000_000.0, 10_000_000.0, 100_000_000.0, 1_000_000_000.0}, + }) + + guardianSignerVerifyLatency = promauto.NewHistogram( + prometheus.HistogramOpts{ + Name: "wormhole_guardian_signer_sig_verify_latency_us", + Help: "Latency histogram for Guardian signature verification requests", + Buckets: []float64{10.0, 20.0, 50.0, 100.0, 1000.0, 5000.0, 10000.0, 100_000.0, 1_000_000.0, 10_000_000.0, 100_000_000.0, 1_000_000_000.0}, + }) +) + +func BenchmarkWrappedSigner(innerSigner GuardianSigner) *BenchmarkSigner { + if innerSigner == nil { + return nil + } + + return &BenchmarkSigner{ + innerSigner: innerSigner, + } +} + +func (b *BenchmarkSigner) Sign(ctx context.Context, hash []byte) ([]byte, error) { + start := time.Now() + sig, err := b.innerSigner.Sign(ctx, hash) + duration := time.Since(start) + + // Add Observation to histogram + guardianSignerSigningLatency.Observe(float64(duration.Microseconds())) + + return sig, err +} + +func (b *BenchmarkSigner) PublicKey(ctx context.Context) ecdsa.PublicKey { + pubKey := b.innerSigner.PublicKey(ctx) + return pubKey +} + +func (b *BenchmarkSigner) Verify(ctx context.Context, sig []byte, hash []byte) (bool, error) { + + start := time.Now() + valid, err := b.innerSigner.Verify(ctx, sig, hash) + duration := time.Since(start) + + // Add observation to histogram + guardianSignerVerifyLatency.Observe(float64(duration.Microseconds())) + + return valid, err +} diff --git a/node/pkg/guardiansigner/filesigner.go b/node/pkg/guardiansigner/filesigner.go index 991eb4674a..f0a02ecfeb 100644 --- a/node/pkg/guardiansigner/filesigner.go +++ b/node/pkg/guardiansigner/filesigner.go @@ -1,6 +1,7 @@ package guardiansigner import ( + "context" "crypto/ecdsa" "errors" "fmt" @@ -15,6 +16,8 @@ import ( "golang.org/x/crypto/openpgp/armor" // nolint ) +// FileSigner is a signer that loads a guardian key from a file. The URI is expected to be +// in the format file://. type FileSigner struct { keyPath string privateKey *ecdsa.PrivateKey @@ -24,7 +27,10 @@ const ( GuardianKeyArmoredBlock = "WORMHOLE GUARDIAN PRIVATE KEY" ) -func NewFileSigner(unsafeDevMode bool, signerKeyPath string) (*FileSigner, error) { +// The FileSigner is a signer that reads a guardian key from a file (signerKeyPath). The key is +// expected to be armored with an OpenPGP armor block, and the key itself is expected to be a +// protobuf-encoded GuardianKey message. +func NewFileSigner(ctx context.Context, unsafeDevMode bool, signerKeyPath string) (*FileSigner, error) { fileSigner := &FileSigner{ keyPath: signerKeyPath, } @@ -67,8 +73,8 @@ func NewFileSigner(unsafeDevMode bool, signerKeyPath string) (*FileSigner, error return fileSigner, nil } -func (fs *FileSigner) Sign(hash []byte) ([]byte, error) { - +// Sign signs a hash using the go-ethereum/crypto package's `Sign` function. +func (fs *FileSigner) Sign(ctx context.Context, hash []byte) ([]byte, error) { // Sign the hash sig, err := crypto.Sign(hash, fs.privateKey) @@ -79,12 +85,15 @@ func (fs *FileSigner) Sign(hash []byte) ([]byte, error) { return sig, nil } -func (fs *FileSigner) PublicKey() ecdsa.PublicKey { +// PublicKey returns the public key of the signer. +func (fs *FileSigner) PublicKey(ctx context.Context) ecdsa.PublicKey { return fs.privateKey.PublicKey } -func (fs *FileSigner) Verify(sig []byte, hash []byte) (bool, error) { - +// Verify verifies a signature against a hash using the go-ethereum/crypto +// package's `SigToPub` function. +func (fs *FileSigner) Verify(ctx context.Context, sig []byte, hash []byte) (bool, error) { + // Recover the public key from the signature. recoveredPubKey, err := ethcrypto.SigToPub(hash, sig) if err != nil { diff --git a/node/pkg/guardiansigner/generatedsigner.go b/node/pkg/guardiansigner/generatedsigner.go index 503fccd5ea..47052201a1 100644 --- a/node/pkg/guardiansigner/generatedsigner.go +++ b/node/pkg/guardiansigner/generatedsigner.go @@ -1,6 +1,7 @@ package guardiansigner import ( + "context" "crypto/ecdsa" "crypto/rand" "fmt" @@ -15,6 +16,8 @@ type GeneratedSigner struct { privateKey *ecdsa.PrivateKey } +// NewGeneratedSigner creates a new GeneratedSigner. If key is nil, a random private key +// is generated. Otherwise, the private key is used as-is. func NewGeneratedSigner(key *ecdsa.PrivateKey) (*GeneratedSigner, error) { if key == nil { privateKey, err := ecdsa.GenerateKey(ethcrypto.S256(), rand.Reader) @@ -25,7 +28,7 @@ func NewGeneratedSigner(key *ecdsa.PrivateKey) (*GeneratedSigner, error) { } -func (gs *GeneratedSigner) Sign(hash []byte) (sig []byte, err error) { +func (gs *GeneratedSigner) Sign(ctx context.Context, hash []byte) (sig []byte, err error) { // Sign the hash sig, err = ethcrypto.Sign(hash, gs.privateKey) @@ -36,11 +39,11 @@ func (gs *GeneratedSigner) Sign(hash []byte) (sig []byte, err error) { return sig, nil } -func (gs *GeneratedSigner) PublicKey() (pubKey ecdsa.PublicKey) { +func (gs *GeneratedSigner) PublicKey(ctx context.Context) (pubKey ecdsa.PublicKey) { return gs.privateKey.PublicKey } -func (gs *GeneratedSigner) Verify(sig []byte, hash []byte) (valid bool, err error) { +func (gs *GeneratedSigner) Verify(ctx context.Context, sig []byte, hash []byte) (valid bool, err error) { recoveredPubKey, err := ethcrypto.SigToPub(hash, sig) if err != nil { diff --git a/node/pkg/guardiansigner/guardiansigner.go b/node/pkg/guardiansigner/guardiansigner.go index 057f9c03bf..79b3dcc781 100644 --- a/node/pkg/guardiansigner/guardiansigner.go +++ b/node/pkg/guardiansigner/guardiansigner.go @@ -1,6 +1,7 @@ package guardiansigner import ( + "context" "crypto/ecdsa" "errors" "fmt" @@ -14,54 +15,93 @@ const ( InvalidSignerType SignerType = iota // file:// FileSignerType + // amazonkms:// + AmazonKmsSignerType ) -// GuardianSigner interface +// GuardianSigner interface. Each function in the GuardianSigner interface +// expects a context to be supplied. This is because signers might interact +// with external services that have the potential of introducing unwanted +// behaviour, like timing out or hanging indefinitely. It's up to each signer +// implementation to decide how to handle the context. type GuardianSigner interface { // Sign expects a keccak256 hash that needs to be signed. - Sign(hash []byte) (sig []byte, err error) - // PublicKey returns the ECDSA public key of the signer. Note that this should not - // be confused with the EVM address. - PublicKey() (pubKey ecdsa.PublicKey) + Sign(ctx context.Context, hash []byte) (sig []byte, err error) + // PublicKey returns the ECDSA public key of the signer. + PublicKey(ctx context.Context) (pubKey ecdsa.PublicKey) // Verify is a convenience function that recovers a public key from the sig/hash pair, // and checks if the public key matches that of the guardian signer. - Verify(sig []byte, hash []byte) (valid bool, err error) + Verify(ctx context.Context, sig []byte, hash []byte) (valid bool, err error) } -func NewGuardianSignerFromUri(signerUri string, unsafeDevMode bool) (GuardianSigner, error) { - - // Get the signer type +// Create a new GuardianSigner from the given URI. The caller can also specify the +// unsafeDevMode flag, which signals that the signer is running in an unsafe development +// environment. This is used, for example, to signal the file signer that it should check +// whether or not the key is deterministic. +// +// Additionally, a context is expected to be supplied, as the signer might interact with +// external services during construction. For example, the Amazon KMS signer validates that +// the ARN is valid and retrieves the public key from the service. +func NewGuardianSignerFromUri(ctx context.Context, signerUri string, unsafeDevMode bool) (GuardianSigner, error) { + // Get the signer type and key configuration. The key configuration + // isn't interpreted as anything in particular here, as each signer + // implementation requires different configurations; i.e., the file + // signer requires a path and the amazon kms signer requires an ARN. signerType, signerKeyConfig, err := ParseSignerUri(signerUri) if err != nil { return nil, err } + var guardianSigner GuardianSigner + + // Create the new guardian signer, based on the signerType. If an invalid + // signer type is supplied, an error is returned; or if the signer creation + // returns an error, the error is bubbled up. switch signerType { case FileSignerType: - return NewFileSigner(unsafeDevMode, signerKeyConfig) + guardianSigner, err = NewFileSigner(ctx, unsafeDevMode, signerKeyConfig) + case AmazonKmsSignerType: + guardianSigner, err = NewAmazonKmsSigner(ctx, unsafeDevMode, signerKeyConfig) default: return nil, errors.New("unsupported guardian signer type") } + + if err != nil { + return nil, err + } + + // Wrap the guardian signer in a benchmark signer, which will record the + // time taken to sign and verify messages. + return BenchmarkWrappedSigner(guardianSigner), nil } +// Parse the signer URI and return the signer type and key configuration. The signer +// URI is expected to be in the format ://. func ParseSignerUri(signerUri string) (signerType SignerType, signerKeyConfig string, err error) { // Split the URI using the standard "://" scheme separator signerUriSplit := strings.Split(signerUri, "://") - // This check is purely for ensuring that there is actually a path separator. + // This check ensures that the URI is in the correct format by checking that the split + // has at least two elements. if len(signerUriSplit) < 2 { return InvalidSignerType, "", errors.New("no path separator in guardian signer URI") } typeStr := signerUriSplit[0] + // Rejoin the remainder of the split URI as the configuration for the guardian signer - // implementation. The remainder of the split is joined using the URI scheme separator. + // implementation. The remainder of the split is joined using the URI scheme separator, as + // the key configuration might require the same separator. keyConfig := strings.Join(signerUriSplit[1:], "://") + // Return the signer type and key configuration. If the signer type is not supported, an + // error is returned. switch typeStr { case "file": return FileSignerType, keyConfig, nil + case "amazonkms": + return AmazonKmsSignerType, keyConfig, nil default: return InvalidSignerType, "", fmt.Errorf("unsupported guardian signer type: %s", typeStr) } diff --git a/node/pkg/guardiansigner/guardiansigner_test.go b/node/pkg/guardiansigner/guardiansigner_test.go index 5e660357ed..2351b293ed 100644 --- a/node/pkg/guardiansigner/guardiansigner_test.go +++ b/node/pkg/guardiansigner/guardiansigner_test.go @@ -1,6 +1,8 @@ package guardiansigner import ( + "context" + "encoding/hex" "testing" "github.com/ethereum/go-ethereum/crypto" @@ -22,6 +24,8 @@ func TestParseSignerUri(t *testing.T) { {label: "FileUriNoSchemeSeparator", path: "filewhatever", expectedType: InvalidSignerType}, {label: "FileUriMultipleSchemeSeparators", path: "file://testing://this://", expectedType: FileSignerType}, {label: "FileUriTraversal", path: "file://../../../file", expectedType: FileSignerType}, + // Amazon KMS + {label: "AmazonKmsURI", path: "amazonkms://some-arn", expectedType: AmazonKmsSignerType}, } for _, testcase := range tests { @@ -45,17 +49,18 @@ func TestFileSignerNonExistentFile(t *testing.T) { nonexistentFileUri := "file://somewhere/on/disk.key" // Attempt to generate signer using top-level generator - _, err := NewGuardianSignerFromUri(nonexistentFileUri, true) + _, err := NewGuardianSignerFromUri(context.Background(), nonexistentFileUri, true) assert.Error(t, err) // Attempt to generate signer using NewFileSigner _, keyPath, _ := ParseSignerUri(nonexistentFileUri) - fileSigner, err := NewFileSigner(true, keyPath) + fileSigner, err := NewFileSigner(context.Background(), true, keyPath) assert.Nil(t, fileSigner) assert.Error(t, err) } func TestFileSigner(t *testing.T) { + ctx := context.Background() fileUri := "file://../query/dev.guardian.key" expectedEthAddress := "0xbeFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe" @@ -66,33 +71,76 @@ func TestFileSigner(t *testing.T) { // matches the expected address. // Attempt to generate signer using top-level generator - fileSigner1, err := NewGuardianSignerFromUri(fileUri, true) + fileSigner1, err := NewGuardianSignerFromUri(ctx, fileUri, true) require.NoError(t, err) assert.NotNil(t, fileSigner1) - assert.Equal(t, ethcrypto.PubkeyToAddress(fileSigner1.PublicKey()).Hex(), expectedEthAddress) + assert.Equal(t, ethcrypto.PubkeyToAddress(fileSigner1.PublicKey(ctx)).Hex(), expectedEthAddress) // Attempt to generate signer using NewFileSigner signerType, keyPath, err := ParseSignerUri(fileUri) assert.Equal(t, signerType, FileSignerType) require.NoError(t, err) - fileSigner2, err := NewFileSigner(true, keyPath) + fileSigner2, err := NewFileSigner(ctx, true, keyPath) require.NoError(t, err) assert.NotNil(t, fileSigner2) - assert.Equal(t, ethcrypto.PubkeyToAddress(fileSigner2.PublicKey()).Hex(), expectedEthAddress) + assert.Equal(t, ethcrypto.PubkeyToAddress(fileSigner2.PublicKey(ctx)).Hex(), expectedEthAddress) // Sign some arbitrary data data := crypto.Keccak256Hash([]byte("data")) - sig, err := fileSigner1.Sign(data.Bytes()) + sig, err := fileSigner1.Sign(ctx, data.Bytes()) assert.NoError(t, err) // Verify the signature - valid, _ := fileSigner1.Verify(sig, data.Bytes()) + valid, _ := fileSigner1.Verify(ctx, sig, data.Bytes()) assert.True(t, valid) // Use generated signature with incorrect hash, should fail arbitraryHash := crypto.Keccak256Hash([]byte("arbitrary hash data")) - valid, _ = fileSigner1.Verify(sig, arbitraryHash.Bytes()) + valid, _ = fileSigner1.Verify(ctx, sig, arbitraryHash.Bytes()) assert.False(t, valid) } + +func TestAmazonKmsAdjustBufferSize(t *testing.T) { + + bytes_30_null_0102, _ := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000102") + bytes_33_01, _ := hex.DecodeString("010101010101010101010101010101010101010101010101010101010101010101") + bytes_32_01, _ := hex.DecodeString("0101010101010101010101010101010101010101010101010101010101010101") + + full_of_null_bytes, _ := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000000") + + tests := []struct { + name string + input []byte + expectedOutput []byte + }{ + { + name: "LeftPadSmallInput", + input: []byte{0x1, 0x2}, + expectedOutput: bytes_30_null_0102, + }, + { + name: "TruncateLargeInput", + input: bytes_33_01, + expectedOutput: bytes_32_01, + }, + { + name: "Leave32ByteInputAsIs", + input: bytes_32_01, + expectedOutput: bytes_32_01, + }, + { + name: "Return32NullBytesOnEmptyInput", + input: []byte{}, + expectedOutput: full_of_null_bytes, + }, + } + + for _, testcase := range tests { + t.Run(testcase.name, func(t *testing.T) { + output := adjustBufferSize(testcase.input) + assert.Equal(t, testcase.expectedOutput, output) + }) + } +} diff --git a/node/pkg/node/adminServiceRunnable.go b/node/pkg/node/adminServiceRunnable.go index 60717b5ab0..d083fbc148 100644 --- a/node/pkg/node/adminServiceRunnable.go +++ b/node/pkg/node/adminServiceRunnable.go @@ -90,7 +90,7 @@ func adminServiceRunnable( gov, evmConnector, guardianSigner, - ethcrypto.PubkeyToAddress(guardianSigner.PublicKey()), + ethcrypto.PubkeyToAddress(guardianSigner.PublicKey(ctx)), rpcMap, ) diff --git a/node/pkg/node/node_test.go b/node/pkg/node/node_test.go index 378f2acc0f..039b7396bb 100644 --- a/node/pkg/node/node_test.go +++ b/node/pkg/node/node_test.go @@ -122,7 +122,7 @@ func newMockGuardianSet(t testing.TB, testId uint, n int) []*mockGuardian { MockObservationC: make(chan *common.MessagePublication), MockSetC: make(chan *common.GuardianSet), guardianSigner: guardianSigner, - guardianAddr: eth_crypto.PubkeyToAddress(guardianSigner.PublicKey()), + guardianAddr: eth_crypto.PubkeyToAddress(guardianSigner.PublicKey(context.Background())), config: createGuardianConfig(t, testId, uint(i)), } } diff --git a/node/pkg/p2p/ccq_p2p.go b/node/pkg/p2p/ccq_p2p.go index f604e33688..8a2d84e376 100644 --- a/node/pkg/p2p/ccq_p2p.go +++ b/node/pkg/p2p/ccq_p2p.go @@ -247,7 +247,7 @@ func (ccq *ccqP2p) publisher(ctx context.Context, guardianSigner guardiansigner. continue } digest := query.GetQueryResponseDigestFromBytes(msgBytes) - sig, err := guardianSigner.Sign(digest.Bytes()) + sig, err := guardianSigner.Sign(ctx, digest.Bytes()) if err != nil { panic(err) } diff --git a/node/pkg/p2p/p2p.go b/node/pkg/p2p/p2p.go index b5ff281b22..c64d56cabc 100644 --- a/node/pkg/p2p/p2p.go +++ b/node/pkg/p2p/p2p.go @@ -491,7 +491,7 @@ func Run(params *RunParams) func(ctx context.Context) error { // Start up heartbeating if it is enabled. if params.nodeName != "" { go func() { - ourAddr := ethcrypto.PubkeyToAddress(params.guardianSigner.PublicKey()) + ourAddr := ethcrypto.PubkeyToAddress(params.guardianSigner.PublicKey(ctx)) ctr := int64(0) // Guardians should send out their first heartbeat immediately to speed up test runs. @@ -568,7 +568,7 @@ func Run(params *RunParams) func(ctx context.Context) error { collectNodeMetrics(ourAddr, h.ID(), heartbeat) if params.gov != nil { - params.gov.CollectMetrics(heartbeat, params.gossipControlSendC, params.guardianSigner, ourAddr) + params.gov.CollectMetrics(ctx, heartbeat, params.gossipControlSendC, params.guardianSigner, ourAddr) } msg := gossipv1.GossipMessage{ @@ -642,7 +642,7 @@ func Run(params *RunParams) func(ctx context.Context) error { // Sign the observation request using our node's guardian key. digest := signedObservationRequestDigest(b) - sig, err := params.guardianSigner.Sign(digest.Bytes()) + sig, err := params.guardianSigner.Sign(ctx, digest.Bytes()) if err != nil { panic(err) } @@ -650,7 +650,7 @@ func Run(params *RunParams) func(ctx context.Context) error { sReq := &gossipv1.SignedObservationRequest{ ObservationRequest: b, Signature: sig, - GuardianAddr: ethcrypto.PubkeyToAddress(params.guardianSigner.PublicKey()).Bytes(), + GuardianAddr: ethcrypto.PubkeyToAddress(params.guardianSigner.PublicKey(ctx)).Bytes(), } envelope := &gossipv1.GossipMessage{ @@ -762,7 +762,7 @@ func Run(params *RunParams) func(ctx context.Context) error { zap.String("from", envelope.GetFrom().String())) } else { guardianAddr := eth_common.BytesToAddress(s.GuardianAddr) - if params.guardianSigner == nil || guardianAddr != ethcrypto.PubkeyToAddress(params.guardianSigner.PublicKey()) { + if params.guardianSigner == nil || guardianAddr != ethcrypto.PubkeyToAddress(params.guardianSigner.PublicKey(ctx)) { prevPeerId, ok := params.components.ProtectedHostByGuardianKey[guardianAddr] if ok { if prevPeerId != peerId { @@ -984,7 +984,8 @@ func Run(params *RunParams) func(ctx context.Context) error { } func createSignedHeartbeat(guardianSigner guardiansigner.GuardianSigner, heartbeat *gossipv1.Heartbeat) *gossipv1.SignedHeartbeat { - ourAddr := ethcrypto.PubkeyToAddress(guardianSigner.PublicKey()) + ctx := context.Background() + ourAddr := ethcrypto.PubkeyToAddress(guardianSigner.PublicKey(ctx)) b, err := proto.Marshal(heartbeat) if err != nil { @@ -993,7 +994,7 @@ func createSignedHeartbeat(guardianSigner guardiansigner.GuardianSigner, heartbe // Sign the heartbeat using our node's guardian signer. digest := heartbeatDigest(b) - sig, err := guardianSigner.Sign(digest.Bytes()) + sig, err := guardianSigner.Sign(ctx, digest.Bytes()) if err != nil { panic(err) } diff --git a/node/pkg/p2p/p2p_test.go b/node/pkg/p2p/p2p_test.go index c77aa1eaf6..767b7efd18 100644 --- a/node/pkg/p2p/p2p_test.go +++ b/node/pkg/p2p/p2p_test.go @@ -1,6 +1,7 @@ package p2p import ( + "context" "testing" "time" @@ -29,7 +30,7 @@ func TestSignedHeartbeat(t *testing.T) { guardianSigner, err := guardiansigner.GenerateSignerWithPrivatekeyUnsafe(nil) assert.NoError(t, err) - gAddr := crypto.PubkeyToAddress(guardianSigner.PublicKey()) + gAddr := crypto.PubkeyToAddress(guardianSigner.PublicKey(context.Background())) fromP2pId, err := peer.Decode("12D3KooWSgMXkhzTbKTeupHYmyG7sFJ5LpVreQcwVnX8RD7LBpy9") assert.NoError(t, err) p2pNodeId, err := fromP2pId.Marshal() @@ -37,7 +38,7 @@ func TestSignedHeartbeat(t *testing.T) { guardianSigner2, err := guardiansigner.GenerateSignerWithPrivatekeyUnsafe(nil) assert.NoError(t, err) - gAddr2 := crypto.PubkeyToAddress(guardianSigner2.PublicKey()) + gAddr2 := crypto.PubkeyToAddress(guardianSigner2.PublicKey(context.Background())) fromP2pId2, err := peer.Decode("12D3KooWDZVv7BhZ8yFLkarNdaSWaB43D6UbQwExJ8nnGAEmfHcU") assert.NoError(t, err) p2pNodeId2, err := fromP2pId2.Marshal() @@ -94,7 +95,7 @@ func TestSignedHeartbeat(t *testing.T) { testFunc := func(t *testing.T, tc testCase) { - addr := crypto.PubkeyToAddress(guardianSigner.PublicKey()) + addr := crypto.PubkeyToAddress(guardianSigner.PublicKey(context.Background())) heartbeat := &gossipv1.Heartbeat{ NodeName: "someNode", diff --git a/node/pkg/p2p/watermark_test.go b/node/pkg/p2p/watermark_test.go index ce4dfadaca..7c99ede7c9 100644 --- a/node/pkg/p2p/watermark_test.go +++ b/node/pkg/p2p/watermark_test.go @@ -119,7 +119,7 @@ func TestWatermark(t *testing.T) { gs[i].components.Port = uint(LOCAL_P2P_PORTRANGE_START + i) gs[i].networkID = "/wormhole/localdev" - guardianset.Keys = append(guardianset.Keys, crypto.PubkeyToAddress(gs[i].guardianSigner.PublicKey())) + guardianset.Keys = append(guardianset.Keys, crypto.PubkeyToAddress(gs[i].guardianSigner.PublicKey(ctx))) id, err := p2ppeer.IDFromPublicKey(gs[0].priv.GetPublic()) require.NoError(t, err) diff --git a/node/pkg/processor/benchmark_test.go b/node/pkg/processor/benchmark_test.go index 5de561a2ee..0c248cb4a7 100644 --- a/node/pkg/processor/benchmark_test.go +++ b/node/pkg/processor/benchmark_test.go @@ -143,9 +143,9 @@ func createProcessorForTest(b *testing.B, numVAAs int, ctx context.Context, db * for count := 0; count < 19; count++ { guardianSigner, err := guardiansigner.GenerateSignerWithPrivatekeyUnsafe(nil) require.NoError(b, err) - keys = append(keys, crypto.PubkeyToAddress(guardianSigner.PublicKey())) + keys = append(keys, crypto.PubkeyToAddress(guardianSigner.PublicKey(ctx))) guardianSigners = append(guardianSigners, guardianSigner) - guardianAddrs = append(guardianAddrs, crypto.PubkeyToAddress(guardianSigner.PublicKey()).Bytes()) + guardianAddrs = append(guardianAddrs, crypto.PubkeyToAddress(guardianSigner.PublicKey(ctx)).Bytes()) if count == 0 { ourSigner = guardianSigner } @@ -179,7 +179,7 @@ func createProcessorForTest(b *testing.B, numVAAs int, ctx context.Context, db * db: db, logger: logger, state: &aggregationState{observationMap{}}, - ourAddr: crypto.PubkeyToAddress(ourSigner.PublicKey()), + ourAddr: crypto.PubkeyToAddress(ourSigner.PublicKey(context.Background())), pythnetVaas: make(map[string]PythNetVaaEntry), updatedVAAs: make(map[string]*updateVaaEntry), gatewayRelayer: gwRelayer, @@ -229,7 +229,7 @@ func (pd *ProcessorData) createObservation(b *testing.B, guardianIdx int, k *com // Sign the digest using our node's guardian signer guardianSigner := pd.guardianSigners[guardianIdx] - signature, err := guardianSigner.Sign(digest.Bytes()) + signature, err := guardianSigner.Sign(context.Background(), digest.Bytes()) require.NoError(b, err) return &gossipv1.Observation{ diff --git a/node/pkg/processor/message.go b/node/pkg/processor/message.go index ed5946a330..933bd3452f 100644 --- a/node/pkg/processor/message.go +++ b/node/pkg/processor/message.go @@ -1,6 +1,7 @@ package processor import ( + "context" "encoding/hex" "time" @@ -69,7 +70,7 @@ func (p *Processor) handleMessage(k *common.MessagePublication) { hash := hex.EncodeToString(digest.Bytes()) // Sign the digest using the node's GuardianSigner - signature, err := p.guardianSigner.Sign(digest.Bytes()) + signature, err := p.guardianSigner.Sign(context.Background(), digest.Bytes()) if err != nil { panic(err) } diff --git a/node/pkg/processor/processor.go b/node/pkg/processor/processor.go index ca5048b4dd..38ef9946a4 100644 --- a/node/pkg/processor/processor.go +++ b/node/pkg/processor/processor.go @@ -253,7 +253,7 @@ func NewProcessor( logger: supervisor.Logger(ctx), state: &aggregationState{observationMap{}}, - ourAddr: crypto.PubkeyToAddress(guardianSigner.PublicKey()), + ourAddr: crypto.PubkeyToAddress(guardianSigner.PublicKey(ctx)), governor: g, acct: acct, acctReadC: acctReadC,