From 69a00908e0eeaa936c5c588c9fc4287f4a8ca117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Fri, 16 Aug 2024 13:09:53 +0200 Subject: [PATCH] Aptos LOOP setup (#14076) * Wire up init necessary for an Aptos LOOP * Fix typings, make sure SetFrom doesn't blank out RawConfig * Add missing initialization code for Aptos * keystore: Aptos should use blake2b since blake2s is unavailable Blake2b costs about a third of SHA2/SHA3/keccak256 https://aptos.dev/en/build/smart-contracts/cryptography * keystore: Fix aptoskey public key encoding * keystore: Remove aptoskey/account.go, completely unused * aptoskey: Add support for address derivation * keystore: aptos: Test implementation against vectors generated by the CLI * keystore: aptos: Remove 0x prefix from public key * keystore: cosmos: Remove duplicate nil check * operator_ui: Increase install timeout Really painful on slower internet connections * keystore: aptos: Update report hashing format --- core/cmd/shell.go | 7 + core/cmd/shell_local.go | 9 ++ core/internal/cltest/cltest.go | 7 + core/services/chainlink/config.go | 24 ++- core/services/chainlink/config_general.go | 4 + core/services/chainlink/config_test.go | 2 +- .../chainlink/mocks/general_config.go | 49 ++++++ .../chainlink/relayer_chain_interoperators.go | 17 +++ core/services/chainlink/relayer_factory.go | 70 ++++++++- core/services/chainlink/types.go | 1 + .../keystore/keys/aptoskey/account.go | 72 --------- .../keystore/keys/aptoskey/account_test.go | 141 ------------------ core/services/keystore/keys/aptoskey/key.go | 13 +- .../keystore/keys/aptoskey/key_test.go | 17 +++ core/services/keystore/keys/cosmoskey/key.go | 3 - .../keystore/keys/ocr2key/aptos_keyring.go | 11 +- core/web/presenters/aptos_key.go | 8 +- operator_ui/install.go | 2 +- 18 files changed, 219 insertions(+), 238 deletions(-) delete mode 100644 core/services/keystore/keys/aptoskey/account.go delete mode 100644 core/services/keystore/keys/aptoskey/account_test.go create mode 100644 core/services/keystore/keys/aptoskey/key_test.go diff --git a/core/cmd/shell.go b/core/cmd/shell.go index 3d055bb03a9..5c864b82cd0 100644 --- a/core/cmd/shell.go +++ b/core/cmd/shell.go @@ -208,6 +208,13 @@ func (n ChainlinkAppFactory) NewApplication(ctx context.Context, cfg chainlink.G } initOps = append(initOps, chainlink.InitStarknet(ctx, relayerFactory, starkCfg)) } + if cfg.AptosEnabled() { + aptosCfg := chainlink.AptosFactoryConfig{ + Keystore: keyStore.Aptos(), + TOMLConfigs: cfg.AptosConfigs(), + } + initOps = append(initOps, chainlink.InitAptos(ctx, relayerFactory, aptosCfg)) + } relayChainInterops, err := chainlink.NewCoreRelayerChainInteroperators(initOps...) if err != nil { diff --git a/core/cmd/shell_local.go b/core/cmd/shell_local.go index ed8b653c05b..604daf75683 100644 --- a/core/cmd/shell_local.go +++ b/core/cmd/shell_local.go @@ -435,6 +435,9 @@ func (s *Shell) runNode(c *cli.Context) error { if s.Config.StarkNetEnabled() { enabledChains = append(enabledChains, chaintype.StarkNet) } + if s.Config.AptosEnabled() { + enabledChains = append(enabledChains, chaintype.Aptos) + } err2 := app.GetKeyStore().OCR2().EnsureKeys(rootCtx, enabledChains...) if err2 != nil { return errors.Wrap(err2, "failed to ensure ocr key") @@ -464,6 +467,12 @@ func (s *Shell) runNode(c *cli.Context) error { return errors.Wrap(err2, "failed to ensure starknet key") } } + if s.Config.AptosEnabled() { + err2 := app.GetKeyStore().Aptos().EnsureKey(rootCtx) + if err2 != nil { + return errors.Wrap(err2, "failed to ensure aptos key") + } + } err2 := app.GetKeyStore().CSA().EnsureKey(rootCtx) if err2 != nil { diff --git a/core/internal/cltest/cltest.go b/core/internal/cltest/cltest.go index 12491300bf7..7447d1385f6 100644 --- a/core/internal/cltest/cltest.go +++ b/core/internal/cltest/cltest.go @@ -439,6 +439,13 @@ func NewApplicationWithConfig(t testing.TB, cfg chainlink.GeneralConfig, flagsAn } initOps = append(initOps, chainlink.InitStarknet(testCtx, relayerFactory, starkCfg)) } + if cfg.AptosEnabled() { + aptosCfg := chainlink.AptosFactoryConfig{ + Keystore: keyStore.Aptos(), + TOMLConfigs: cfg.AptosConfigs(), + } + initOps = append(initOps, chainlink.InitAptos(testCtx, relayerFactory, aptosCfg)) + } relayChainInterops, err := chainlink.NewCoreRelayerChainInteroperators(initOps...) if err != nil { diff --git a/core/services/chainlink/config.go b/core/services/chainlink/config.go index dc301c0967f..476e758ccbb 100644 --- a/core/services/chainlink/config.go +++ b/core/services/chainlink/config.go @@ -55,8 +55,13 @@ type RawConfig map[string]any // ValidateConfig returns an error if the Config is not valid for use, as-is. func (c *RawConfig) ValidateConfig() (err error) { if v, ok := (*c)["Enabled"]; ok { - if _, ok := v.(*bool); !ok { - err = multierr.Append(err, commonconfig.ErrInvalid{Name: "Enabled", Value: v, Msg: "expected *bool"}) + if _, ok := v.(bool); !ok { + err = multierr.Append(err, commonconfig.ErrInvalid{Name: "Enabled", Value: v, Msg: "expected bool"}) + } + } + if v, ok := (*c)["ChainID"]; ok { + if _, ok := v.(string); !ok { + err = multierr.Append(err, commonconfig.ErrInvalid{Name: "ChainID", Value: v, Msg: "expected string"}) } } return err @@ -67,7 +72,17 @@ func (c *RawConfig) IsEnabled() bool { return false } - return (*c)["Enabled"] == nil || *(*c)["Enabled"].(*bool) + enabled, ok := (*c)["Enabled"].(bool) + return ok && enabled +} + +func (c *RawConfig) ChainID() string { + if c == nil { + return "" + } + + chainID, _ := (*c)["ChainID"].(string) + return chainID } // TOMLString returns a TOML encoded string. @@ -170,6 +185,9 @@ func (c *Config) SetFrom(f *Config) (err error) { err = multierr.Append(err, commonconfig.NamedMultiErrorList(err4, "Starknet")) } + // the plugin should handle it's own defaults and merging + c.Aptos = f.Aptos + _, err = commonconfig.MultiErrorList(err) return err diff --git a/core/services/chainlink/config_general.go b/core/services/chainlink/config_general.go index 5b6b839fb5e..79c92f82145 100644 --- a/core/services/chainlink/config_general.go +++ b/core/services/chainlink/config_general.go @@ -209,6 +209,10 @@ func (g *generalConfig) StarknetConfigs() starknet.TOMLConfigs { return g.c.Starknet } +func (g *generalConfig) AptosConfigs() RawConfigs { + return g.c.Aptos +} + func (g *generalConfig) Validate() error { return g.validate(g.secrets.Validate) } diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index f5a9d335928..5fc9babe77b 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -1365,7 +1365,7 @@ func TestConfig_Validate(t *testing.T) { - 1: 2 errors: - ChainID: missing: required for all chains - Nodes: missing: must have at least one node - - Aptos.0.Enabled: invalid value (1): expected *bool`}, + - Aptos.0.Enabled: invalid value (1): expected bool`}, } { t.Run(tt.name, func(t *testing.T) { var c Config diff --git a/core/services/chainlink/mocks/general_config.go b/core/services/chainlink/mocks/general_config.go index c42ed5c7014..f4594a43225 100644 --- a/core/services/chainlink/mocks/general_config.go +++ b/core/services/chainlink/mocks/general_config.go @@ -4,6 +4,8 @@ package mocks import ( chainlinkconfig "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" + chainlink "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" + config "github.com/smartcontractkit/chainlink/v2/core/config" cosmosconfig "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" @@ -81,6 +83,53 @@ func (_c *GeneralConfig_AppID_Call) RunAndReturn(run func() uuid.UUID) *GeneralC return _c } +// AptosConfigs provides a mock function with given fields: +func (_m *GeneralConfig) AptosConfigs() chainlink.RawConfigs { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for AptosConfigs") + } + + var r0 chainlink.RawConfigs + if rf, ok := ret.Get(0).(func() chainlink.RawConfigs); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(chainlink.RawConfigs) + } + } + + return r0 +} + +// GeneralConfig_AptosConfigs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AptosConfigs' +type GeneralConfig_AptosConfigs_Call struct { + *mock.Call +} + +// AptosConfigs is a helper method to define mock.On call +func (_e *GeneralConfig_Expecter) AptosConfigs() *GeneralConfig_AptosConfigs_Call { + return &GeneralConfig_AptosConfigs_Call{Call: _e.mock.On("AptosConfigs")} +} + +func (_c *GeneralConfig_AptosConfigs_Call) Run(run func()) *GeneralConfig_AptosConfigs_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *GeneralConfig_AptosConfigs_Call) Return(_a0 chainlink.RawConfigs) *GeneralConfig_AptosConfigs_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *GeneralConfig_AptosConfigs_Call) RunAndReturn(run func() chainlink.RawConfigs) *GeneralConfig_AptosConfigs_Call { + _c.Call.Return(run) + return _c +} + // AptosEnabled provides a mock function with given fields: func (_m *GeneralConfig) AptosEnabled() bool { ret := _m.Called() diff --git a/core/services/chainlink/relayer_chain_interoperators.go b/core/services/chainlink/relayer_chain_interoperators.go index 60381c0d479..ffcfc67b87d 100644 --- a/core/services/chainlink/relayer_chain_interoperators.go +++ b/core/services/chainlink/relayer_chain_interoperators.go @@ -186,6 +186,23 @@ func InitStarknet(ctx context.Context, factory RelayerFactory, config StarkNetFa } } +// InitAptos is a option for instantiating Aptos relayers +func InitAptos(ctx context.Context, factory RelayerFactory, config AptosFactoryConfig) CoreRelayerChainInitFunc { + return func(op *CoreRelayerChainInteroperators) (err error) { + relayers, err := factory.NewAptos(config.Keystore, config.TOMLConfigs) + if err != nil { + return fmt.Errorf("failed to setup aptos relayer: %w", err) + } + + for id, relayer := range relayers { + op.srvs = append(op.srvs, relayer) + op.loopRelayers[id] = relayer + } + + return nil + } +} + // Get a [loop.Relayer] by id func (rs *CoreRelayerChainInteroperators) Get(id types.RelayID) (loop.Relayer, error) { rs.mu.Lock() diff --git a/core/services/chainlink/relayer_factory.go b/core/services/chainlink/relayer_factory.go index 849964f9bec..3ddfe270477 100644 --- a/core/services/chainlink/relayer_factory.go +++ b/core/services/chainlink/relayer_factory.go @@ -18,7 +18,7 @@ import ( solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" pkgstarknet "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink" starkchain "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/chain" - "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" + starkcfg "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" coreconfig "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/config/env" @@ -171,12 +171,12 @@ func (r *RelayerFactory) NewSolana(ks keystore.Solana, chainCfgs solcfg.TOMLConf type StarkNetFactoryConfig struct { Keystore keystore.StarkNet - config.TOMLConfigs + starkcfg.TOMLConfigs } // TODO BCF-2606 consider consolidating the driving logic with that of NewSolana above via generics // perhaps when we implement a Cosmos LOOP -func (r *RelayerFactory) NewStarkNet(ks keystore.StarkNet, chainCfgs config.TOMLConfigs) (map[types.RelayID]loop.Relayer, error) { +func (r *RelayerFactory) NewStarkNet(ks keystore.StarkNet, chainCfgs starkcfg.TOMLConfigs) (map[types.RelayID]loop.Relayer, error) { starknetRelayers := make(map[types.RelayID]loop.Relayer) var ( @@ -205,7 +205,7 @@ func (r *RelayerFactory) NewStarkNet(ks keystore.StarkNet, chainCfgs config.TOML if cmdName := env.StarknetPlugin.Cmd.Get(); cmdName != "" { // setup the starknet relayer to be a LOOP cfgTOML, err := toml.Marshal(struct { - Starknet config.TOMLConfig + Starknet starkcfg.TOMLConfig }{Starknet: *chainCfg}) if err != nil { return nil, fmt.Errorf("failed to marshal StarkNet configs: %w", err) @@ -301,3 +301,65 @@ func (r *RelayerFactory) NewCosmos(config CosmosFactoryConfig) (map[types.RelayI } return relayers, nil } + +type AptosFactoryConfig struct { + Keystore keystore.Aptos + TOMLConfigs RawConfigs +} + +func (r *RelayerFactory) NewAptos(ks keystore.Aptos, chainCfgs RawConfigs) (map[types.RelayID]loop.Relayer, error) { + plugin := env.NewPlugin("aptos") + loopKs := &keystore.AptosLooppSigner{Aptos: ks} + return r.NewLOOPRelayer("Aptos", corerelay.NetworkAptos, plugin, loopKs, chainCfgs) +} + +func (r *RelayerFactory) NewLOOPRelayer(name string, network string, plugin env.Plugin, ks coretypes.Keystore, chainCfgs RawConfigs) (map[types.RelayID]loop.Relayer, error) { + relayers := make(map[types.RelayID]loop.Relayer) + lggr := r.Logger.Named(name) + + unique := make(map[string]struct{}) + // create one relayer per chain id + for _, chainCfg := range chainCfgs { + relayID := types.RelayID{Network: network, ChainID: chainCfg.ChainID()} + if _, alreadyExists := unique[relayID.Name()]; alreadyExists { + return nil, fmt.Errorf("duplicate chain definitions for %s", relayID.Name()) + } + unique[relayID.Name()] = struct{}{} + + // skip disabled chains from further processing + if !chainCfg.IsEnabled() { + lggr.Warnw("Skipping disabled chain", "id", relayID.ChainID) + continue + } + + lggr2 := lggr.Named(relayID.ChainID) + + cmdName := plugin.Cmd.Get() + if cmdName == "" { + return nil, fmt.Errorf("plugin not defined: %s", "") + } + + // setup the relayer as a LOOP + cfgTOML, err := toml.Marshal(chainCfg) + if err != nil { + return nil, fmt.Errorf("failed to marshal configs: %w", err) + } + + envVars, err := plugins.ParseEnvFile(plugin.Env.Get()) + if err != nil { + return nil, fmt.Errorf("failed to parse env file: %w", err) + } + cmdFn, err := plugins.NewCmdFactory(r.Register, plugins.CmdConfig{ + ID: relayID.Name(), + Cmd: cmdName, + Env: envVars, + }) + if err != nil { + return nil, fmt.Errorf("failed to create LOOP command: %w", err) + } + // the relayer service has a delicate keystore dependency. the value that is passed to NewRelayerService must + // be compatible with instantiating a starknet transaction manager KeystoreAdapter within the LOOPp executable. + relayers[relayID] = loop.NewRelayerService(lggr2, r.GRPCOpts, cmdFn, string(cfgTOML), ks, r.CapabilitiesRegistry) + } + return relayers, nil +} diff --git a/core/services/chainlink/types.go b/core/services/chainlink/types.go index 4aa37825493..74ffc5dc66d 100644 --- a/core/services/chainlink/types.go +++ b/core/services/chainlink/types.go @@ -15,6 +15,7 @@ type GeneralConfig interface { CosmosConfigs() coscfg.TOMLConfigs SolanaConfigs() solcfg.TOMLConfigs StarknetConfigs() stkcfg.TOMLConfigs + AptosConfigs() RawConfigs // ConfigTOML returns both the user provided and effective configuration as TOML. ConfigTOML() (user, effective string) } diff --git a/core/services/keystore/keys/aptoskey/account.go b/core/services/keystore/keys/aptoskey/account.go deleted file mode 100644 index 89f62d33011..00000000000 --- a/core/services/keystore/keys/aptoskey/account.go +++ /dev/null @@ -1,72 +0,0 @@ -package aptoskey - -import ( - "encoding/hex" - "errors" - "fmt" - "strings" -) - -// AccountAddress is a 32 byte address on the Aptos blockchain -// It can represent an Object, an Account, and much more. -// -// AccountAddress is copied from the aptos sdk because: -// 1. There are still breaking changes in sdk and we don't want the dependency. -// 2. AccountAddress is just a wrapper and can be easily extracted out. -// -// https://github.com/aptos-labs/aptos-go-sdk/blob/main/internal/types/account.go -type AccountAddress [32]byte - -// IsSpecial Returns whether the address is a "special" address. Addresses are considered -// special if the first 63 characters of the hex string are zero. In other words, -// an address is special if the first 31 bytes are zero and the last byte is -// smaller than `0b10000` (16). In other words, special is defined as an address -// that matches the following regex: `^0x0{63}[0-9a-f]$`. In short form this means -// the addresses in the range from `0x0` to `0xf` (inclusive) are special. -// For more details see the v1 address standard defined as part of AIP-40: -// https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-40.md -func (aa *AccountAddress) IsSpecial() bool { - for _, b := range aa[:31] { - if b != 0 { - return false - } - } - return aa[31] < 0x10 -} - -// String Returns the canonical string representation of the AccountAddress -func (aa *AccountAddress) String() string { - if aa.IsSpecial() { - return fmt.Sprintf("0x%x", aa[31]) - } - return BytesToHex(aa[:]) -} - -// ParseStringRelaxed parses a string into an AccountAddress -func (aa *AccountAddress) ParseStringRelaxed(x string) error { - x = strings.TrimPrefix(x, "0x") - if len(x) < 1 { - return ErrAddressTooShort - } - if len(x) > 64 { - return ErrAddressTooLong - } - if len(x)%2 != 0 { - x = "0" + x - } - bytes, err := hex.DecodeString(x) - if err != nil { - return err - } - // zero-prefix/right-align what bytes we got - copy((*aa)[32-len(bytes):], bytes) - - return nil -} - -var ErrAddressTooShort = errors.New("AccountAddress too short") -var ErrAddressTooLong = errors.New("AccountAddress too long") - -func BytesToHex(bytes []byte) string { - return "0x" + hex.EncodeToString(bytes) -} diff --git a/core/services/keystore/keys/aptoskey/account_test.go b/core/services/keystore/keys/aptoskey/account_test.go deleted file mode 100644 index b9ed4ea04a5..00000000000 --- a/core/services/keystore/keys/aptoskey/account_test.go +++ /dev/null @@ -1,141 +0,0 @@ -package aptoskey - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -// Tests extracted from -// https://github.com/aptos-labs/aptos-go-sdk/blob/5ee5ac308e5881b508c0a5124f5e0b8df27a4d40/internal/types/account_test.go - -func TestStringOutput(t *testing.T) { - inputs := [][]byte{ - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F}, - {0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, - {0x02, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, - {0x00, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, - {0x00, 0x04, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, - {0x00, 0x00, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, - } - expected := []string{ - "0x0", - "0x1", - "0xf", - "0x1234123412341234123412341234123412341234123412340123456789abcdef", - "0x0234123412341234123412341234123412341234123412340123456789abcdef", - "0x0034123412341234123412341234123412341234123412340123456789abcdef", - "0x0004123412341234123412341234123412341234123412340123456789abcdef", - "0x0000123412341234123412341234123412341234123412340123456789abcdef", - } - - for i := 0; i < len(inputs); i++ { - addr := AccountAddress(inputs[i]) - assert.Equal(t, expected[i], addr.String()) - } -} - -func TestAccountAddress_ParseStringRelaxed_Error(t *testing.T) { - var owner AccountAddress - err := owner.ParseStringRelaxed("0x") - assert.Error(t, err) - err = owner.ParseStringRelaxed("0xF1234567812345678123456781234567812345678123456781234567812345678") - assert.Error(t, err) - err = owner.ParseStringRelaxed("NotHex") - assert.Error(t, err) -} - -func TestAccountAddress_String(t *testing.T) { - testCases := []struct { - name string - address AccountAddress - expected string - }{ - { - name: "Special address", - address: AccountAddress{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, - expected: "0x1", - }, - { - name: "Non-special address", - address: AccountAddress{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, - expected: "0x123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.expected, tc.address.String()) - }) - } -} - -func TestAccountAddress_IsSpecial(t *testing.T) { - testCases := []struct { - name string - address AccountAddress - expected bool - }{ - { - name: "Special address", - address: AccountAddress{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, - expected: true, - }, - { - name: "Non-special address", - address: AccountAddress{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, - expected: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.expected, tc.address.IsSpecial()) - }) - } -} - -func TestBytesToHex(t *testing.T) { - testCases := []struct { - name string - bytes []byte - expected string - }{ - { - name: "Empty bytes", - bytes: []byte{}, - expected: "0x", - }, - { - name: "Non-empty bytes", - bytes: []byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, - expected: "0x123456789abcdef0", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.expected, BytesToHex(tc.bytes)) - }) - } -} - -func TestAccountSpecialString(t *testing.T) { - var aa AccountAddress - aa[31] = 3 - aas := aa.String() - if aas != "0x3" { - t.Errorf("wanted 0x3 got %s", aas) - } - - var aa2 AccountAddress - err := aa2.ParseStringRelaxed("0x3") - if err != nil { - t.Errorf("unexpected err %s", err) - } - if aa2 != aa { - t.Errorf("aa2 != aa") - } -} diff --git a/core/services/keystore/keys/aptoskey/key.go b/core/services/keystore/keys/aptoskey/key.go index ec9b27a3596..fa8925454e4 100644 --- a/core/services/keystore/keys/aptoskey/key.go +++ b/core/services/keystore/keys/aptoskey/key.go @@ -7,7 +7,7 @@ import ( "fmt" "io" - "github.com/mr-tron/base58" + "golang.org/x/crypto/sha3" ) // Raw represents the Aptos private key @@ -37,6 +37,7 @@ var _ fmt.GoStringer = &Key{} // Key represents Aptos key type Key struct { + // TODO: store initial Account() derivation to support key rotation privkey ed25519.PrivateKey pubKey ed25519.PublicKey } @@ -72,14 +73,20 @@ func (key Key) ID() string { return key.PublicKeyStr() } +// https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-40.md#long +func (key Key) Account() string { + authKey := sha3.Sum256(append([]byte(key.pubKey), 0x00)) + return fmt.Sprintf("%064x", authKey) +} + // GetPublic get Key's public key func (key Key) GetPublic() ed25519.PublicKey { return key.pubKey } -// PublicKeyStr return base58 encoded public key +// PublicKeyStr returns hex encoded public key func (key Key) PublicKeyStr() string { - return base58.Encode(key.pubKey) + return fmt.Sprintf("%064x", key.pubKey) } // Raw returns the seed from private key diff --git a/core/services/keystore/keys/aptoskey/key_test.go b/core/services/keystore/keys/aptoskey/key_test.go new file mode 100644 index 00000000000..277a30eb9f3 --- /dev/null +++ b/core/services/keystore/keys/aptoskey/key_test.go @@ -0,0 +1,17 @@ +package aptoskey + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAptosKey(t *testing.T) { + bytes, err := hex.DecodeString("f0d07ab448018b2754475f9a3b580218b0675a1456aad96ad607c7bbd7d9237b") + require.NoError(t, err) + k := Raw(bytes).Key() + assert.Equal(t, k.PublicKeyStr(), "2acd605efc181e2af8a0b8c0686a5e12578efa1253d15a235fa5e5ad970c4b29") + assert.Equal(t, k.Account(), "69d8b07f5945185873c622ea66873b0e1fb921de7b94d904d3ef9be80770682e") +} diff --git a/core/services/keystore/keys/cosmoskey/key.go b/core/services/keystore/keys/cosmoskey/key.go index 3e516a21ab5..b5ea255f23f 100644 --- a/core/services/keystore/keys/cosmoskey/key.go +++ b/core/services/keystore/keys/cosmoskey/key.go @@ -59,9 +59,6 @@ func newFrom(reader io.Reader) Key { panic(err) } privKey := secpSigningAlgo.Generate()(rawKey.D.Bytes()) - if err != nil { - panic(err) - } return Key{ d: rawKey.D, diff --git a/core/services/keystore/keys/ocr2key/aptos_keyring.go b/core/services/keystore/keys/ocr2key/aptos_keyring.go index 51e6c3a9c8f..cdc5afa792f 100644 --- a/core/services/keystore/keys/ocr2key/aptos_keyring.go +++ b/core/services/keystore/keys/ocr2key/aptos_keyring.go @@ -2,12 +2,11 @@ package ocr2key import ( "crypto/ed25519" - "encoding/binary" "io" "github.com/hdevalence/ed25519consensus" "github.com/pkg/errors" - "golang.org/x/crypto/blake2s" + "golang.org/x/crypto/blake2b" "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -37,17 +36,15 @@ func (akr *aptosKeyring) PublicKey() ocrtypes.OnchainPublicKey { func (akr *aptosKeyring) reportToSigData(reportCtx ocrtypes.ReportContext, report ocrtypes.Report) ([]byte, error) { rawReportContext := evmutil.RawReportContext(reportCtx) - h, err := blake2s.New256(nil) + h, err := blake2b.New256(nil) if err != nil { return nil, err } - reportLen := make([]byte, 4) - binary.BigEndian.PutUint32(reportLen[0:], uint32(len(report))) - h.Write(reportLen[:]) - h.Write(report) + // blake2b_256(report_context | report) h.Write(rawReportContext[0][:]) h.Write(rawReportContext[1][:]) h.Write(rawReportContext[2][:]) + h.Write(report) return h.Sum(nil), nil } diff --git a/core/web/presenters/aptos_key.go b/core/web/presenters/aptos_key.go index 6460c325f97..8c0c09ed10a 100644 --- a/core/web/presenters/aptos_key.go +++ b/core/web/presenters/aptos_key.go @@ -5,7 +5,8 @@ import "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/apt // AptosKeyResource represents a Aptos key JSONAPI resource. type AptosKeyResource struct { JAID - PubKey string `json:"publicKey"` + Account string `json:"account"` + PubKey string `json:"publicKey"` } // GetName implements the api2go EntityNamer interface @@ -15,8 +16,9 @@ func (AptosKeyResource) GetName() string { func NewAptosKeyResource(key aptoskey.Key) *AptosKeyResource { r := &AptosKeyResource{ - JAID: JAID{ID: key.ID()}, - PubKey: key.PublicKeyStr(), + JAID: JAID{ID: key.ID()}, + Account: key.Account(), + PubKey: key.PublicKeyStr(), } return r diff --git a/operator_ui/install.go b/operator_ui/install.go index 1e09783db66..14d920ddd43 100644 --- a/operator_ui/install.go +++ b/operator_ui/install.go @@ -22,7 +22,7 @@ func main() { fullRepo = owner + "/" + repo tagPath = "operator_ui/TAG" unpackDir = "core/web/assets" - downloadTimeoutSeconds = 10 + downloadTimeoutSeconds = 30 ) // Grab first argument as root directory if len(os.Args) < 2 {