signer: enable typed data signing from signer rpc (#26241)
This PR should makes it easier to sign EIP-712 typed data via the accounts.Wallet API, by using the mimetype for typed data. Co-authored-by: nasdf <keenan.nemetz@gmail.com>
This commit is contained in:
parent
add1bff13f
commit
6a4e05c93a
@ -18,6 +18,7 @@ package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime"
|
||||
@ -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
|
||||
}
|
||||
@ -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)
|
||||
@ -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
|
||||
}
|
||||
@ -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.
|
||||
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
if signature == nil || len(signature) != 65 {
|
||||
} else if signature == nil || len(signature) != 65 {
|
||||
t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
|
||||
} else {
|
||||
want = signature
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user