Commit 229c33f6 authored by Ralph Pichler's avatar Ralph Pichler Committed by GitHub

clef: allow specifying account (#1313)

parent bacba66c
...@@ -17,38 +17,39 @@ import ( ...@@ -17,38 +17,39 @@ import (
) )
const ( const (
optionNameDataDir = "data-dir" optionNameDataDir = "data-dir"
optionNameDBCapacity = "db-capacity" optionNameDBCapacity = "db-capacity"
optionNamePassword = "password" optionNamePassword = "password"
optionNamePasswordFile = "password-file" optionNamePasswordFile = "password-file"
optionNameAPIAddr = "api-addr" optionNameAPIAddr = "api-addr"
optionNameP2PAddr = "p2p-addr" optionNameP2PAddr = "p2p-addr"
optionNameNATAddr = "nat-addr" optionNameNATAddr = "nat-addr"
optionNameP2PWSEnable = "p2p-ws-enable" optionNameP2PWSEnable = "p2p-ws-enable"
optionNameP2PQUICEnable = "p2p-quic-enable" optionNameP2PQUICEnable = "p2p-quic-enable"
optionNameDebugAPIEnable = "debug-api-enable" optionNameDebugAPIEnable = "debug-api-enable"
optionNameDebugAPIAddr = "debug-api-addr" optionNameDebugAPIAddr = "debug-api-addr"
optionNameBootnodes = "bootnode" optionNameBootnodes = "bootnode"
optionNameNetworkID = "network-id" optionNameNetworkID = "network-id"
optionWelcomeMessage = "welcome-message" optionWelcomeMessage = "welcome-message"
optionCORSAllowedOrigins = "cors-allowed-origins" optionCORSAllowedOrigins = "cors-allowed-origins"
optionNameStandalone = "standalone" optionNameStandalone = "standalone"
optionNameTracingEnabled = "tracing-enable" optionNameTracingEnabled = "tracing-enable"
optionNameTracingEndpoint = "tracing-endpoint" optionNameTracingEndpoint = "tracing-endpoint"
optionNameTracingServiceName = "tracing-service-name" optionNameTracingServiceName = "tracing-service-name"
optionNameVerbosity = "verbosity" optionNameVerbosity = "verbosity"
optionNameGlobalPinningEnabled = "global-pinning-enable" optionNameGlobalPinningEnabled = "global-pinning-enable"
optionNamePaymentThreshold = "payment-threshold" optionNamePaymentThreshold = "payment-threshold"
optionNamePaymentTolerance = "payment-tolerance" optionNamePaymentTolerance = "payment-tolerance"
optionNamePaymentEarly = "payment-early" optionNamePaymentEarly = "payment-early"
optionNameResolverEndpoints = "resolver-options" optionNameResolverEndpoints = "resolver-options"
optionNameGatewayMode = "gateway-mode" optionNameGatewayMode = "gateway-mode"
optionNameClefSignerEnable = "clef-signer-enable" optionNameClefSignerEnable = "clef-signer-enable"
optionNameClefSignerEndpoint = "clef-signer-endpoint" optionNameClefSignerEndpoint = "clef-signer-endpoint"
optionNameSwapEndpoint = "swap-endpoint" optionNameClefSignerEthereumAddress = "clef-signer-ethereum-address"
optionNameSwapFactoryAddress = "swap-factory-address" optionNameSwapEndpoint = "swap-endpoint"
optionNameSwapInitialDeposit = "swap-initial-deposit" optionNameSwapFactoryAddress = "swap-factory-address"
optionNameSwapEnable = "swap-enable" optionNameSwapInitialDeposit = "swap-initial-deposit"
optionNameSwapEnable = "swap-enable"
) )
func init() { func init() {
...@@ -204,6 +205,7 @@ func (c *command) setAllFlags(cmd *cobra.Command) { ...@@ -204,6 +205,7 @@ func (c *command) setAllFlags(cmd *cobra.Command) {
cmd.Flags().Bool(optionNameGatewayMode, false, "disable a set of sensitive features in the api") cmd.Flags().Bool(optionNameGatewayMode, false, "disable a set of sensitive features in the api")
cmd.Flags().Bool(optionNameClefSignerEnable, false, "enable clef signer") cmd.Flags().Bool(optionNameClefSignerEnable, false, "enable clef signer")
cmd.Flags().String(optionNameClefSignerEndpoint, "", "clef signer endpoint") cmd.Flags().String(optionNameClefSignerEndpoint, "", "clef signer endpoint")
cmd.Flags().String(optionNameClefSignerEthereumAddress, "", "ethereum address to use from clef signer")
cmd.Flags().String(optionNameSwapEndpoint, "http://localhost:8545", "swap ethereum blockchain endpoint") cmd.Flags().String(optionNameSwapEndpoint, "http://localhost:8545", "swap ethereum blockchain endpoint")
cmd.Flags().String(optionNameSwapFactoryAddress, "", "swap factory address") cmd.Flags().String(optionNameSwapFactoryAddress, "", "swap factory address")
cmd.Flags().String(optionNameSwapInitialDeposit, "100000000000000000", "initial deposit if deploying a new chequebook") cmd.Flags().String(optionNameSwapInitialDeposit, "100000000000000000", "initial deposit if deploying a new chequebook")
......
...@@ -19,6 +19,7 @@ import ( ...@@ -19,6 +19,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/accounts/external" "github.com/ethereum/go-ethereum/accounts/external"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/ethersphere/bee" "github.com/ethersphere/bee"
"github.com/ethersphere/bee/pkg/crypto" "github.com/ethersphere/bee/pkg/crypto"
...@@ -320,7 +321,15 @@ func (c *command) configureSigner(cmd *cobra.Command, logger logging.Logger) (co ...@@ -320,7 +321,15 @@ func (c *command) configureSigner(cmd *cobra.Command, logger logging.Logger) (co
return nil, err return nil, err
} }
signer, err = clef.NewSigner(externalSigner, clefRPC, crypto.Recover) wantedAddress := c.config.GetString(optionNameClefSignerEthereumAddress)
var overlayEthAddress *common.Address = nil
// if wantedAddress was specified use that, otherwise clef account 0 will be selected.
if wantedAddress != "" {
ethAddress := common.HexToAddress(wantedAddress)
overlayEthAddress = &ethAddress
}
signer, err = clef.NewSigner(externalSigner, clefRPC, crypto.Recover, overlayEthAddress)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -21,8 +21,9 @@ import ( ...@@ -21,8 +21,9 @@ import (
) )
var ( var (
ErrNoAccounts = errors.New("no accounts found in clef") ErrNoAccounts = errors.New("no accounts found in clef")
clefRecoveryMessage = []byte("public key recovery message") ErrAccountNotAvailable = errors.New("account not available in clef")
clefRecoveryMessage = []byte("public key recovery message")
) )
// ExternalSignerInterface is the interface for the clef client from go-ethereum. // ExternalSignerInterface is the interface for the clef client from go-ethereum.
...@@ -66,17 +67,34 @@ func DefaultIpcPath() (string, error) { ...@@ -66,17 +67,34 @@ func DefaultIpcPath() (string, error) {
return filepath.Join(home, ".clef", socket), nil return filepath.Join(home, ".clef", socket), nil
} }
// NewSigner creates a new connection to the signer at endpoint. func selectAccount(clef ExternalSignerInterface, ethAddress *common.Address) (accounts.Account, error) {
// As clef does not expose public keys it signs a test message to recover the public key.
func NewSigner(clef ExternalSignerInterface, client Client, recoverFunc crypto.RecoverFunc) (signer crypto.Signer, err error) {
// get the list of available ethereum accounts // get the list of available ethereum accounts
clefAccounts := clef.Accounts() clefAccounts := clef.Accounts()
if len(clefAccounts) == 0 { if len(clefAccounts) == 0 {
return nil, ErrNoAccounts return accounts.Account{}, ErrNoAccounts
}
if ethAddress == nil {
// pick the first account as the one we use
return clefAccounts[0], nil
}
for _, availableAccount := range clefAccounts {
if availableAccount.Address == *ethAddress {
return availableAccount, nil
}
} }
return accounts.Account{}, ErrAccountNotAvailable
}
// pick the first account as the one we use // NewSigner creates a new connection to the signer at endpoint.
account := clefAccounts[0] // If ethAddress is nil the account with index 0 will be selected. Otherwise it will verify the requested account actually exists.
// As clef does not expose public keys it signs a test message to recover the public key.
func NewSigner(clef ExternalSignerInterface, client Client, recoverFunc crypto.RecoverFunc, ethAddress *common.Address) (signer crypto.Signer, err error) {
account, err := selectAccount(clef, ethAddress)
if err != nil {
return nil, err
}
// clef currently does not expose the public key // clef currently does not expose the public key
// sign some data so we can recover it // sign some data so we can recover it
......
...@@ -59,6 +59,9 @@ func TestNewClefSigner(t *testing.T) { ...@@ -59,6 +59,9 @@ func TestNewClefSigner(t *testing.T) {
{ {
Address: ethAddress, Address: ethAddress,
}, },
{
Address: common.Address{},
},
}, },
signature: testSignature, signature: testSignature,
} }
...@@ -72,7 +75,7 @@ func TestNewClefSigner(t *testing.T) { ...@@ -72,7 +75,7 @@ func TestNewClefSigner(t *testing.T) {
t.Fatalf("wrong data used for recover. expected %v got %v", clef.ClefRecoveryMessage, data) t.Fatalf("wrong data used for recover. expected %v got %v", clef.ClefRecoveryMessage, data)
} }
return publicKey, nil return publicKey, nil
}) }, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -99,12 +102,91 @@ func TestNewClefSigner(t *testing.T) { ...@@ -99,12 +102,91 @@ func TestNewClefSigner(t *testing.T) {
} }
} }
func TestNewClefSignerSpecificAccount(t *testing.T) {
ethAddress := common.HexToAddress("0x31415b599f636129AD03c196cef9f8f8b184D5C7")
wantedAddress := common.HexToAddress("0x41415b599f636129AD03c196cef9f8f8b184D5C7")
testSignature := make([]byte, 65)
key, err := crypto.GenerateSecp256k1Key()
if err != nil {
t.Fatal(err)
}
publicKey := &key.PublicKey
mock := &mockClef{
accounts: []accounts.Account{
{
Address: ethAddress,
},
{
Address: wantedAddress,
},
},
signature: testSignature,
}
signer, err := clef.NewSigner(mock, nil, func(signature, data []byte) (*ecdsa.PublicKey, error) {
if !bytes.Equal(testSignature, signature) {
t.Fatalf("wrong data used for recover. expected %v got %v", testSignature, signature)
}
if !bytes.Equal(clef.ClefRecoveryMessage, data) {
t.Fatalf("wrong data used for recover. expected %v got %v", clef.ClefRecoveryMessage, data)
}
return publicKey, nil
}, &wantedAddress)
if err != nil {
t.Fatal(err)
}
if mock.signedAccount.Address != wantedAddress {
t.Fatalf("wrong account used for signing. expected %v got %v", wantedAddress, mock.signedAccount.Address)
}
if mock.signedMimeType != accounts.MimetypeTextPlain {
t.Fatalf("wrong mime type used for signing. expected %v got %v", accounts.MimetypeTextPlain, mock.signedMimeType)
}
if !bytes.Equal(mock.signedData, clef.ClefRecoveryMessage) {
t.Fatalf("wrong data used for signing. expected %v got %v", clef.ClefRecoveryMessage, mock.signedData)
}
signerPublicKey, err := signer.PublicKey()
if err != nil {
t.Fatal(err)
}
if signerPublicKey != publicKey {
t.Fatalf("wrong public key. expected %v got %v", publicKey, signerPublicKey)
}
}
func TestNewClefSignerAccountUnavailable(t *testing.T) {
ethAddress := common.HexToAddress("0x31415b599f636129AD03c196cef9f8f8b184D5C7")
wantedAddress := common.HexToAddress("0x41415b599f636129AD03c196cef9f8f8b184D5C7")
mock := &mockClef{
accounts: []accounts.Account{
{
Address: ethAddress,
},
},
}
_, err := clef.NewSigner(mock, nil, func(signature, data []byte) (*ecdsa.PublicKey, error) {
return nil, errors.New("called sign")
}, &wantedAddress)
if !errors.Is(err, clef.ErrAccountNotAvailable) {
t.Fatalf("expected account to be not available. got error %v", err)
}
}
func TestClefNoAccounts(t *testing.T) { func TestClefNoAccounts(t *testing.T) {
mock := &mockClef{ mock := &mockClef{
accounts: []accounts.Account{}, accounts: []accounts.Account{},
} }
_, err := clef.NewSigner(mock, nil, nil) _, err := clef.NewSigner(mock, nil, nil, nil)
if err == nil { if err == nil {
t.Fatal("expected ErrNoAccounts error if no accounts") t.Fatal("expected ErrNoAccounts error if no accounts")
} }
...@@ -158,7 +240,7 @@ func TestClefTypedData(t *testing.T) { ...@@ -158,7 +240,7 @@ func TestClefTypedData(t *testing.T) {
}, },
}, func(signature, data []byte) (*ecdsa.PublicKey, error) { }, func(signature, data []byte) (*ecdsa.PublicKey, error) {
return publicKey, nil return publicKey, nil
}) }, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment