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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"mime"
|
"mime"
|
||||||
@ -135,11 +136,7 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType
|
|||||||
req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Messages: messages, Hash: sighash}
|
req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Messages: messages, Hash: sighash}
|
||||||
case apitypes.ApplicationClique.Mime:
|
case apitypes.ApplicationClique.Mime:
|
||||||
// Clique is the Ethereum PoA standard
|
// Clique is the Ethereum PoA standard
|
||||||
stringData, ok := data.(string)
|
cliqueData, err := fromHex(data)
|
||||||
if !ok {
|
|
||||||
return nil, useEthereumV, fmt.Errorf("input for %v must be an hex-encoded string", apitypes.ApplicationClique.Mime)
|
|
||||||
}
|
|
||||||
cliqueData, err := hexutil.Decode(stringData)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, useEthereumV, err
|
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
|
// Clique uses V on the form 0 or 1
|
||||||
useEthereumV = false
|
useEthereumV = false
|
||||||
req = &SignDataRequest{ContentType: mediaType, Rawdata: cliqueRlp, Messages: messages, Hash: sighash}
|
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:
|
default: // also case TextPlain.Mime:
|
||||||
// Calculates an Ethereum ECDSA signature for:
|
// Calculates an Ethereum ECDSA signature for:
|
||||||
// hash = keccak256("\x19Ethereum Signed Message:\n${message length}${message}")
|
// hash = keccak256("\x19Ethereum Signed Message:\n${message length}${message}")
|
||||||
// We expect it to be a string
|
// We expect input to be a hex-encoded string
|
||||||
if stringData, ok := data.(string); !ok {
|
textData, err := fromHex(data)
|
||||||
return nil, useEthereumV, fmt.Errorf("input for text/plain must be an hex-encoded string")
|
if err != nil {
|
||||||
} else {
|
return nil, useEthereumV, err
|
||||||
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}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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.Address = addr
|
||||||
req.Meta = MetadataFromContext(ctx)
|
req.Meta = MetadataFromContext(ctx)
|
||||||
@ -233,20 +233,12 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd
|
|||||||
// - the signature preimage (hash)
|
// - the signature preimage (hash)
|
||||||
func (api *SignerAPI) signTypedData(ctx context.Context, addr common.MixedcaseAddress,
|
func (api *SignerAPI) signTypedData(ctx context.Context, addr common.MixedcaseAddress,
|
||||||
typedData apitypes.TypedData, validationMessages *apitypes.ValidationMessages) (hexutil.Bytes, hexutil.Bytes, error) {
|
typedData apitypes.TypedData, validationMessages *apitypes.ValidationMessages) (hexutil.Bytes, hexutil.Bytes, error) {
|
||||||
sighash, rawData, err := apitypes.TypedDataAndHash(typedData)
|
req, err := typedDataRequest(typedData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
messages, err := typedData.Format()
|
req.Address = addr
|
||||||
if err != nil {
|
req.Meta = MetadataFromContext(ctx)
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
req := &SignDataRequest{
|
|
||||||
ContentType: apitypes.DataTyped.Mime,
|
|
||||||
Rawdata: []byte(rawData),
|
|
||||||
Messages: messages,
|
|
||||||
Hash: sighash,
|
|
||||||
Address: addr}
|
|
||||||
if validationMessages != nil {
|
if validationMessages != nil {
|
||||||
req.Callinfo = validationMessages.Messages
|
req.Callinfo = validationMessages.Messages
|
||||||
}
|
}
|
||||||
@ -255,7 +247,46 @@ func (api *SignerAPI) signTypedData(ctx context.Context, addr common.MixedcaseAd
|
|||||||
api.UI.ShowError(err.Error())
|
api.UI.ShowError(err.Error())
|
||||||
return nil, nil, err
|
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.
|
// EcRecover recovers the address associated with the given sig.
|
||||||
@ -293,30 +324,20 @@ func UnmarshalValidatorData(data interface{}) (apitypes.ValidatorData, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return apitypes.ValidatorData{}, errors.New("validator input is not a map[string]interface{}")
|
return apitypes.ValidatorData{}, errors.New("validator input is not a map[string]interface{}")
|
||||||
}
|
}
|
||||||
addr, ok := raw["address"].(string)
|
addrBytes, err := fromHex(raw["address"])
|
||||||
if !ok {
|
|
||||||
return apitypes.ValidatorData{}, errors.New("validator address is not sent as a string")
|
|
||||||
}
|
|
||||||
addrBytes, err := hexutil.Decode(addr)
|
|
||||||
if err != nil {
|
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")
|
return apitypes.ValidatorData{}, errors.New("validator address is undefined")
|
||||||
}
|
}
|
||||||
|
messageBytes, err := fromHex(raw["message"])
|
||||||
message, ok := raw["message"].(string)
|
|
||||||
if !ok {
|
|
||||||
return apitypes.ValidatorData{}, errors.New("message is not sent as a string")
|
|
||||||
}
|
|
||||||
messageBytes, err := hexutil.Decode(message)
|
|
||||||
if err != nil {
|
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{}, errors.New("message is undefined")
|
||||||
}
|
}
|
||||||
|
|
||||||
return apitypes.ValidatorData{
|
return apitypes.ValidatorData{
|
||||||
Address: common.BytesToAddress(addrBytes),
|
Address: common.BytesToAddress(addrBytes),
|
||||||
Message: messageBytes,
|
Message: messageBytes,
|
||||||
|
@ -220,15 +220,29 @@ func TestSignData(t *testing.T) {
|
|||||||
if signature == nil || len(signature) != 65 {
|
if signature == nil || len(signature) != 65 {
|
||||||
t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
|
t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
|
||||||
}
|
}
|
||||||
// data/typed
|
// data/typed via SignTypeData
|
||||||
control.approveCh <- "Y"
|
control.approveCh <- "Y"
|
||||||
control.inputCh <- "a_long_password"
|
control.inputCh <- "a_long_password"
|
||||||
signature, err = api.SignTypedData(context.Background(), a, typedData)
|
var want []byte
|
||||||
if err != nil {
|
if signature, err = api.SignTypedData(context.Background(), a, typedData); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
} else if signature == nil || len(signature) != 65 {
|
||||||
if signature == nil || len(signature) != 65 {
|
|
||||||
t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
|
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