Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions cmd/clef/extapi_changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
### Changelog for external API

### 5.0.0

* The external method `accounts_Sign(address, data)` was replaced with `accounts_signData(contentType, address, data)`.
The addition of `contentType` makes it possible to use the method for different types of objects, such as
* signing clique headers,
* signing [ERC-712](https://eips.ethereum.org/EIPS/eip-712) typed data structures (not yet implemented)

#### 4.0.0

* The external `account_Ecrecover`-method was removed.
Expand Down
35 changes: 33 additions & 2 deletions cmd/clef/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,17 @@ import (
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/console"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/signer/core"
"github.com/ethereum/go-ethereum/signer/rules"
"github.com/ethereum/go-ethereum/signer/storage"
"gopkg.in/urfave/cli.v1"
"math/big"
)

// ExternalAPIVersion -- see extapi_changelog.md
Expand Down Expand Up @@ -631,10 +634,38 @@ func testExternalUI(api *core.SignerAPI) {
}
var err error

cliqueHeader := types.Header{
common.HexToHash("0000H45H"),
common.HexToHash("0000H45H"),
common.HexToAddress("0000H45H"),
common.HexToHash("0000H00H"),
common.HexToHash("0000H45H"),
common.HexToHash("0000H45H"),
types.Bloom{},
big.NewInt(1337),
big.NewInt(1337),
1338,
1338,
big.NewInt(1338),
[]byte("Extra data Extra data Extra data Extra data Extra data Extra data Extra data Extra data"),
common.HexToHash("0x0000H45H"),
types.BlockNonce{},
}
cliqueRlp, err := rlp.EncodeToBytes(cliqueHeader)
if err != nil {
utils.Fatalf("Should not error: %v", err)
}
addr, err:= common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
if err != nil {
utils.Fatalf("Should not error: %v", err)
}
_, err = api.SignData(ctx, "application/clique", *addr, cliqueRlp)
checkErr("SignData", err)

_, err = api.SignTransaction(ctx, core.SendTxArgs{From: common.MixedcaseAddress{}}, nil)
checkErr("SignTransaction", err)
_, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304"))
checkErr("Sign", err)
//_, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304"))
//checkErr("Sign", err)
_, err = api.List(ctx)
checkErr("List", err)
_, err = api.New(ctx)
Expand Down
116 changes: 98 additions & 18 deletions signer/core/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,13 @@ import (
"github.com/ethereum/go-ethereum/accounts/usbwallet"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"mime"
)

// numberOfAccountsToDerive For hardware wallets, the number of accounts to derive
Expand All @@ -48,7 +51,9 @@ type ExternalAPI interface {
// SignTransaction request to sign the specified transaction
SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error)
// Sign - request to sign the given data (plus prefix)
Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error)
SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error)
// EcRecover - request to perform ecrecover
//EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error)
// Export - request to export an account
Export(ctx context.Context, addr common.Address) (json.RawMessage, error)
// Import - request to import an account
Expand Down Expand Up @@ -170,11 +175,12 @@ type (
NewPassword string `json:"new_password"`
}
SignDataRequest struct {
Address common.MixedcaseAddress `json:"address"`
Rawdata hexutil.Bytes `json:"raw_data"`
Message string `json:"message"`
Hash hexutil.Bytes `json:"hash"`
Meta Metadata `json:"meta"`
Address common.MixedcaseAddress `json:"address"`
Rawdata hexutil.Bytes `json:"raw_data"`
Message string `json:"message"`
Hash hexutil.Bytes `json:"hash"`
Meta Metadata `json:"meta"`
ContentType string `json:"content_type"`
}
SignDataResponse struct {
Approved bool `json:"approved"`
Expand Down Expand Up @@ -510,22 +516,96 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth

}

// Sign calculates an Ethereum ECDSA signature for:
// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message))
// cliqueSigHash returns the hash which is used as input for the proof-of-authority
// signing. It is the hash of the entire header apart from the 65 byte signature
// contained at the end of the extra data.
//
// Note, the produced signature conforms to the secp256k1 curve R, S and V values,
// where the V value will be 27 or 28 for legacy reasons.
// The method requires the extra data to be at least 65 bytes -- the original implementation
// in clique.go panics if this is the case, thus it's been reimplemented here to avoid the panic
// and simply return an error instead
func cliqueSigHash(header *types.Header) (hexutil.Bytes, error) {
hash := common.Hash{}
if len(header.Extra) < 65 {
return hash.Bytes(), fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra))
}
hasher := sha3.NewKeccak256()
rlp.Encode(hasher, []interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra[:len(header.Extra)-65],
header.MixDigest,
header.Nonce,
})
hasher.Sum(hash[:0])
return hash.Bytes(), nil
}

func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil.Bytes) (*SignDataRequest, error) {
var req *SignDataRequest
mediaType, _, err := mime.ParseMediaType(contentType)
if err != nil {
return nil, err
}
switch mediaType {
case "application/clique":
header := &types.Header{}
if err := rlp.DecodeBytes(data, header); err != nil {
return nil, err
}
sighash, err := cliqueSigHash(header)
if err != nil {
return nil, err
}
msg := fmt.Sprintf("Clique block %d [0x%x]", header.Number, header.Hash())
req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType}
case "text/plain":
// Sign calculates an Ethereum ECDSA signature for:
// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message))

// In the cases where it matter ensure that the charset is handled. The charset
// resides in the 'params' returned as the second returnvalue from mime.ParseMediaType
// charset, ok := params["charset"]
// As it is now, we accept any charset and just treat it as 'raw'.

sighash, msg := SignHash(data)
req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType}
default:
//TODO! Add a content-type for EIP712 typed data.
return nil, fmt.Errorf("content type '%s' not implemented for signing")
}
return req, nil

}

// SignData signs the hash of the provided data, but does so differently
// depending on the content-type specified.
//
// The key used to calculate the signature is decrypted with the given password.
// Depending on the content-type, different types of validations will occur.
//
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) {
sighash, msg := SignHash(data)
// Note, the produced signature conforms to the secp256k1 curve R, S and V values,
// where the V value will be 27 or 28 for legacy reasons.
func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) {

var req, err = api.determineSignatureFormat(contentType, data)
if err != nil {
return nil, err
}
req.Address = addr
req.Meta = MetadataFromContext(ctx)

// We make the request prior to looking up if we actually have the account, to prevent
// account-enumeration via the API
req := &SignDataRequest{Address: addr, Rawdata: data, Message: msg, Hash: sighash, Meta: MetadataFromContext(ctx)}
res, err := api.UI.ApproveSignData(req)

if err != nil {
return nil, err
}
Expand All @@ -538,8 +618,8 @@ func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, da
if err != nil {
return nil, err
}
// Assemble sign the data with the wallet
signature, err := wallet.SignHashWithPassphrase(account, res.Password, sighash)
// Sign the data with the wallet
signature, err := wallet.SignHashWithPassphrase(account, res.Password, req.Hash)
if err != nil {
api.UI.ShowError(err.Error())
return nil, err
Expand Down
6 changes: 3 additions & 3 deletions signer/core/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,15 +259,15 @@ func TestSignData(t *testing.T) {

control <- "Y"
control <- "wrongpassword"
h, err := api.Sign(context.Background(), a, []byte("EHLO world"))
h, err := api.SignData(context.Background(), "text/plain", a, []byte("EHLO world"))
if h != nil {
t.Errorf("Expected nil-data, got %x", h)
}
if err != keystore.ErrDecrypt {
t.Errorf("Expected ErrLocked! %v", err)
}
control <- "No way"
h, err = api.Sign(context.Background(), a, []byte("EHLO world"))
h, err = api.SignData(context.Background(), "text/plain", a, []byte("EHLO world"))
if h != nil {
t.Errorf("Expected nil-data, got %x", h)
}
Expand All @@ -276,7 +276,7 @@ func TestSignData(t *testing.T) {
}
control <- "Y"
control <- "a_long_password"
h, err = api.Sign(context.Background(), a, []byte("EHLO world"))
h, err = api.SignData(context.Background(), "text/plain", a, []byte("EHLO world"))
if err != nil {
t.Fatal(err)
}
Expand Down
10 changes: 5 additions & 5 deletions signer/core/auditlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ func (l *AuditLogger) SignTransaction(ctx context.Context, args SendTxArgs, meth
return res, e
}

func (l *AuditLogger) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) {
l.log.Info("Sign", "type", "request", "metadata", MetadataFromContext(ctx).String(),
"addr", addr.String(), "data", common.Bytes2Hex(data))
b, e := l.api.Sign(ctx, addr, data)
l.log.Info("Sign", "type", "response", "data", common.Bytes2Hex(b), "error", e)
func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) {
l.log.Info("SignData", "type", "request", "metadata", MetadataFromContext(ctx).String(),
"addr", addr.String(), "data", common.Bytes2Hex(data), "content-type", contentType)
b, e := l.api.SignData(ctx, contentType, addr, data)
l.log.Info("SignData", "type", "response", "data", common.Bytes2Hex(b), "error", e)
return b, e
}

Expand Down