Skip to content
Merged
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
121 changes: 71 additions & 50 deletions signer/core/signed_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package core

import (
"context"
"encoding/json"
"errors"
"fmt"
"mime"
Expand Down Expand Up @@ -135,11 +136,7 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType
req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Messages: messages, Hash: sighash}
case apitypes.ApplicationClique.Mime:
// Clique is the Ethereum PoA standard
stringData, ok := data.(string)
if !ok {
return nil, useEthereumV, fmt.Errorf("input for %v must be an hex-encoded string", apitypes.ApplicationClique.Mime)
}
cliqueData, err := hexutil.Decode(stringData)
cliqueData, err := fromHex(data)
if err != nil {
return nil, useEthereumV, err
}
Expand Down Expand Up @@ -167,27 +164,30 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType
// Clique uses V on the form 0 or 1
useEthereumV = false
req = &SignDataRequest{ContentType: mediaType, Rawdata: cliqueRlp, Messages: messages, Hash: sighash}
case apitypes.DataTyped.Mime:
// EIP-712 conformant typed data
var err error
req, err = typedDataRequest(data)
if err != nil {
return nil, useEthereumV, err
}
default: // also case TextPlain.Mime:
// Calculates an Ethereum ECDSA signature for:
// hash = keccak256("\x19Ethereum Signed Message:\n${message length}${message}")
// We expect it to be a string
if stringData, ok := data.(string); !ok {
return nil, useEthereumV, fmt.Errorf("input for text/plain must be an hex-encoded string")
} else {
if textData, err := hexutil.Decode(stringData); err != nil {
return nil, useEthereumV, err
} else {
sighash, msg := accounts.TextAndHash(textData)
messages := []*apitypes.NameValueType{
{
Name: "message",
Typ: accounts.MimetypeTextPlain,
Value: msg,
},
}
req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Messages: messages, Hash: sighash}
}
// We expect input to be a hex-encoded string
textData, err := fromHex(data)
if err != nil {
return nil, useEthereumV, err
}
sighash, msg := accounts.TextAndHash(textData)
messages := []*apitypes.NameValueType{
{
Name: "message",
Typ: accounts.MimetypeTextPlain,
Value: msg,
},
}
req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Messages: messages, Hash: sighash}
}
req.Address = addr
req.Meta = MetadataFromContext(ctx)
Expand Down Expand Up @@ -233,20 +233,12 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd
// - the signature preimage (hash)
func (api *SignerAPI) signTypedData(ctx context.Context, addr common.MixedcaseAddress,
typedData apitypes.TypedData, validationMessages *apitypes.ValidationMessages) (hexutil.Bytes, hexutil.Bytes, error) {
sighash, rawData, err := apitypes.TypedDataAndHash(typedData)
req, err := typedDataRequest(typedData)
if err != nil {
return nil, nil, err
}
messages, err := typedData.Format()
if err != nil {
return nil, nil, err
}
req := &SignDataRequest{
ContentType: apitypes.DataTyped.Mime,
Rawdata: []byte(rawData),
Messages: messages,
Hash: sighash,
Address: addr}
req.Address = addr
req.Meta = MetadataFromContext(ctx)
if validationMessages != nil {
req.Callinfo = validationMessages.Messages
}
Expand All @@ -255,7 +247,46 @@ func (api *SignerAPI) signTypedData(ctx context.Context, addr common.MixedcaseAd
api.UI.ShowError(err.Error())
return nil, nil, err
}
return signature, sighash, nil
return signature, req.Hash, nil
}

// fromHex tries to interpret the data as type string, and convert from
// hexadecimal to []byte
func fromHex(data any) ([]byte, error) {
if stringData, ok := data.(string); ok {
binary, err := hexutil.Decode(stringData)
return binary, err
}
return nil, fmt.Errorf("wrong type %T", data)
}

// typeDataRequest tries to convert the data into a SignDataRequest.
func typedDataRequest(data any) (*SignDataRequest, error) {
var typedData apitypes.TypedData
if td, ok := data.(apitypes.TypedData); ok {
typedData = td
} else { // Hex-encoded data
jsonData, err := fromHex(data)
if err != nil {
return nil, err
}
if err = json.Unmarshal(jsonData, &typedData); err != nil {
return nil, err
}
}
messages, err := typedData.Format()
if err != nil {
return nil, err
}
sighash, rawData, err := apitypes.TypedDataAndHash(typedData)
if err != nil {
return nil, err
}
return &SignDataRequest{
ContentType: apitypes.DataTyped.Mime,
Rawdata: []byte(rawData),
Messages: messages,
Hash: sighash}, nil
}

// EcRecover recovers the address associated with the given sig.
Expand Down Expand Up @@ -293,30 +324,20 @@ func UnmarshalValidatorData(data interface{}) (apitypes.ValidatorData, error) {
if !ok {
return apitypes.ValidatorData{}, errors.New("validator input is not a map[string]interface{}")
}
addr, ok := raw["address"].(string)
if !ok {
return apitypes.ValidatorData{}, errors.New("validator address is not sent as a string")
}
addrBytes, err := hexutil.Decode(addr)
addrBytes, err := fromHex(raw["address"])
if err != nil {
return apitypes.ValidatorData{}, err
return apitypes.ValidatorData{}, fmt.Errorf("validator address error: %w", err)
}
if !ok || len(addrBytes) == 0 {
if len(addrBytes) == 0 {
return apitypes.ValidatorData{}, errors.New("validator address is undefined")
}

message, ok := raw["message"].(string)
if !ok {
return apitypes.ValidatorData{}, errors.New("message is not sent as a string")
}
messageBytes, err := hexutil.Decode(message)
messageBytes, err := fromHex(raw["message"])
if err != nil {
return apitypes.ValidatorData{}, err
return apitypes.ValidatorData{}, fmt.Errorf("message error: %w", err)
}
if !ok || len(messageBytes) == 0 {
if len(messageBytes) == 0 {
return apitypes.ValidatorData{}, errors.New("message is undefined")
}

return apitypes.ValidatorData{
Address: common.BytesToAddress(addrBytes),
Message: messageBytes,
Expand Down
22 changes: 18 additions & 4 deletions signer/core/signed_data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,15 +220,29 @@ func TestSignData(t *testing.T) {
if signature == nil || len(signature) != 65 {
t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
}
// data/typed
// data/typed via SignTypeData
control.approveCh <- "Y"
control.inputCh <- "a_long_password"
signature, err = api.SignTypedData(context.Background(), a, typedData)
if err != nil {
var want []byte
if signature, err = api.SignTypedData(context.Background(), a, typedData); err != nil {
t.Fatal(err)
} else if signature == nil || len(signature) != 65 {
t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
} else {
want = signature
}
if signature == nil || len(signature) != 65 {

// data/typed via SignData / mimetype typed data
control.approveCh <- "Y"
control.inputCh <- "a_long_password"
if typedDataJson, err := json.Marshal(typedData); err != nil {
t.Fatal(err)
} else if signature, err = api.SignData(context.Background(), apitypes.DataTyped.Mime, a, hexutil.Encode(typedDataJson)); err != nil {
t.Fatal(err)
} else if signature == nil || len(signature) != 65 {
t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
} else if have := signature; !bytes.Equal(have, want) {
t.Fatalf("want %x, have %x", want, have)
}
}

Expand Down