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 (
)
const (
optionNameDataDir = "data-dir"
optionNameDBCapacity = "db-capacity"
optionNamePassword = "password"
optionNamePasswordFile = "password-file"
optionNameAPIAddr = "api-addr"
optionNameP2PAddr = "p2p-addr"
optionNameNATAddr = "nat-addr"
optionNameP2PWSEnable = "p2p-ws-enable"
optionNameP2PQUICEnable = "p2p-quic-enable"
optionNameDebugAPIEnable = "debug-api-enable"
optionNameDebugAPIAddr = "debug-api-addr"
optionNameBootnodes = "bootnode"
optionNameNetworkID = "network-id"
optionWelcomeMessage = "welcome-message"
optionCORSAllowedOrigins = "cors-allowed-origins"
optionNameStandalone = "standalone"
optionNameTracingEnabled = "tracing-enable"
optionNameTracingEndpoint = "tracing-endpoint"
optionNameTracingServiceName = "tracing-service-name"
optionNameVerbosity = "verbosity"
optionNameGlobalPinningEnabled = "global-pinning-enable"
optionNamePaymentThreshold = "payment-threshold"
optionNamePaymentTolerance = "payment-tolerance"
optionNamePaymentEarly = "payment-early"
optionNameResolverEndpoints = "resolver-options"
optionNameGatewayMode = "gateway-mode"
optionNameClefSignerEnable = "clef-signer-enable"
optionNameClefSignerEndpoint = "clef-signer-endpoint"
optionNameSwapEndpoint = "swap-endpoint"
optionNameSwapFactoryAddress = "swap-factory-address"
optionNameSwapInitialDeposit = "swap-initial-deposit"
optionNameSwapEnable = "swap-enable"
optionNameDataDir = "data-dir"
optionNameDBCapacity = "db-capacity"
optionNamePassword = "password"
optionNamePasswordFile = "password-file"
optionNameAPIAddr = "api-addr"
optionNameP2PAddr = "p2p-addr"
optionNameNATAddr = "nat-addr"
optionNameP2PWSEnable = "p2p-ws-enable"
optionNameP2PQUICEnable = "p2p-quic-enable"
optionNameDebugAPIEnable = "debug-api-enable"
optionNameDebugAPIAddr = "debug-api-addr"
optionNameBootnodes = "bootnode"
optionNameNetworkID = "network-id"
optionWelcomeMessage = "welcome-message"
optionCORSAllowedOrigins = "cors-allowed-origins"
optionNameStandalone = "standalone"
optionNameTracingEnabled = "tracing-enable"
optionNameTracingEndpoint = "tracing-endpoint"
optionNameTracingServiceName = "tracing-service-name"
optionNameVerbosity = "verbosity"
optionNameGlobalPinningEnabled = "global-pinning-enable"
optionNamePaymentThreshold = "payment-threshold"
optionNamePaymentTolerance = "payment-tolerance"
optionNamePaymentEarly = "payment-early"
optionNameResolverEndpoints = "resolver-options"
optionNameGatewayMode = "gateway-mode"
optionNameClefSignerEnable = "clef-signer-enable"
optionNameClefSignerEndpoint = "clef-signer-endpoint"
optionNameClefSignerEthereumAddress = "clef-signer-ethereum-address"
optionNameSwapEndpoint = "swap-endpoint"
optionNameSwapFactoryAddress = "swap-factory-address"
optionNameSwapInitialDeposit = "swap-initial-deposit"
optionNameSwapEnable = "swap-enable"
)
func init() {
......@@ -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(optionNameClefSignerEnable, false, "enable clef signer")
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(optionNameSwapFactoryAddress, "", "swap factory address")
cmd.Flags().String(optionNameSwapInitialDeposit, "100000000000000000", "initial deposit if deploying a new chequebook")
......
......@@ -19,6 +19,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/accounts/external"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethersphere/bee"
"github.com/ethersphere/bee/pkg/crypto"
......@@ -320,7 +321,15 @@ func (c *command) configureSigner(cmd *cobra.Command, logger logging.Logger) (co
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 {
return nil, err
}
......
......@@ -21,8 +21,9 @@ import (
)
var (
ErrNoAccounts = errors.New("no accounts found in clef")
clefRecoveryMessage = []byte("public key recovery message")
ErrNoAccounts = errors.New("no accounts found in clef")
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.
......@@ -66,17 +67,34 @@ func DefaultIpcPath() (string, error) {
return filepath.Join(home, ".clef", socket), nil
}
// NewSigner creates a new connection to the signer at endpoint.
// 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) {
func selectAccount(clef ExternalSignerInterface, ethAddress *common.Address) (accounts.Account, error) {
// get the list of available ethereum accounts
clefAccounts := clef.Accounts()
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
account := clefAccounts[0]
// NewSigner creates a new connection to the signer at endpoint.
// 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
// sign some data so we can recover it
......
......@@ -59,6 +59,9 @@ func TestNewClefSigner(t *testing.T) {
{
Address: ethAddress,
},
{
Address: common.Address{},
},
},
signature: testSignature,
}
......@@ -72,7 +75,7 @@ func TestNewClefSigner(t *testing.T) {
t.Fatalf("wrong data used for recover. expected %v got %v", clef.ClefRecoveryMessage, data)
}
return publicKey, nil
})
}, nil)
if err != nil {
t.Fatal(err)
}
......@@ -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) {
mock := &mockClef{
accounts: []accounts.Account{},
}
_, err := clef.NewSigner(mock, nil, nil)
_, err := clef.NewSigner(mock, nil, nil, nil)
if err == nil {
t.Fatal("expected ErrNoAccounts error if no accounts")
}
......@@ -158,7 +240,7 @@ func TestClefTypedData(t *testing.T) {
},
}, func(signature, data []byte) (*ecdsa.PublicKey, error) {
return publicKey, nil
})
}, nil)
if err != nil {
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