|
|
|
@ -17,24 +17,14 @@ |
|
|
|
|
package core |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"bytes" |
|
|
|
|
"context" |
|
|
|
|
"errors" |
|
|
|
|
"fmt" |
|
|
|
|
"math/big" |
|
|
|
|
"mime" |
|
|
|
|
"reflect" |
|
|
|
|
"regexp" |
|
|
|
|
"sort" |
|
|
|
|
"strconv" |
|
|
|
|
"strings" |
|
|
|
|
"unicode" |
|
|
|
|
"unicode/utf8" |
|
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/accounts" |
|
|
|
|
"github.com/ethereum/go-ethereum/common" |
|
|
|
|
"github.com/ethereum/go-ethereum/common/hexutil" |
|
|
|
|
"github.com/ethereum/go-ethereum/common/math" |
|
|
|
|
"github.com/ethereum/go-ethereum/consensus/clique" |
|
|
|
|
"github.com/ethereum/go-ethereum/core/types" |
|
|
|
|
"github.com/ethereum/go-ethereum/crypto" |
|
|
|
@ -42,88 +32,6 @@ import ( |
|
|
|
|
"github.com/ethereum/go-ethereum/signer/core/apitypes" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
type SigFormat struct { |
|
|
|
|
Mime string |
|
|
|
|
ByteVersion byte |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var ( |
|
|
|
|
IntendedValidator = SigFormat{ |
|
|
|
|
accounts.MimetypeDataWithValidator, |
|
|
|
|
0x00, |
|
|
|
|
} |
|
|
|
|
DataTyped = SigFormat{ |
|
|
|
|
accounts.MimetypeTypedData, |
|
|
|
|
0x01, |
|
|
|
|
} |
|
|
|
|
ApplicationClique = SigFormat{ |
|
|
|
|
accounts.MimetypeClique, |
|
|
|
|
0x02, |
|
|
|
|
} |
|
|
|
|
TextPlain = SigFormat{ |
|
|
|
|
accounts.MimetypeTextPlain, |
|
|
|
|
0x45, |
|
|
|
|
} |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
type ValidatorData struct { |
|
|
|
|
Address common.Address |
|
|
|
|
Message hexutil.Bytes |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type TypedData struct { |
|
|
|
|
Types Types `json:"types"` |
|
|
|
|
PrimaryType string `json:"primaryType"` |
|
|
|
|
Domain TypedDataDomain `json:"domain"` |
|
|
|
|
Message TypedDataMessage `json:"message"` |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type Type struct { |
|
|
|
|
Name string `json:"name"` |
|
|
|
|
Type string `json:"type"` |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (t *Type) isArray() bool { |
|
|
|
|
return strings.HasSuffix(t.Type, "[]") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// typeName returns the canonical name of the type. If the type is 'Person[]', then
|
|
|
|
|
// this method returns 'Person'
|
|
|
|
|
func (t *Type) typeName() string { |
|
|
|
|
if strings.HasSuffix(t.Type, "[]") { |
|
|
|
|
return strings.TrimSuffix(t.Type, "[]") |
|
|
|
|
} |
|
|
|
|
return t.Type |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (t *Type) isReferenceType() bool { |
|
|
|
|
if len(t.Type) == 0 { |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
// Reference types must have a leading uppercase character
|
|
|
|
|
r, _ := utf8.DecodeRuneInString(t.Type) |
|
|
|
|
return unicode.IsUpper(r) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type Types map[string][]Type |
|
|
|
|
|
|
|
|
|
type TypePriority struct { |
|
|
|
|
Type string |
|
|
|
|
Value uint |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type TypedDataMessage = map[string]interface{} |
|
|
|
|
|
|
|
|
|
type TypedDataDomain struct { |
|
|
|
|
Name string `json:"name"` |
|
|
|
|
Version string `json:"version"` |
|
|
|
|
ChainId *math.HexOrDecimal256 `json:"chainId"` |
|
|
|
|
VerifyingContract string `json:"verifyingContract"` |
|
|
|
|
Salt string `json:"salt"` |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var typedDataReferenceTypeRegexp = regexp.MustCompile(`^[A-Z](\w*)(\[\])?$`) |
|
|
|
|
|
|
|
|
|
// sign receives a request and produces a signature
|
|
|
|
|
//
|
|
|
|
|
// Note, the produced signature conforms to the secp256k1 curve R, S and V values,
|
|
|
|
@ -195,14 +103,14 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
switch mediaType { |
|
|
|
|
case IntendedValidator.Mime: |
|
|
|
|
case apitypes.IntendedValidator.Mime: |
|
|
|
|
// Data with an intended validator
|
|
|
|
|
validatorData, err := UnmarshalValidatorData(data) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, useEthereumV, err |
|
|
|
|
} |
|
|
|
|
sighash, msg := SignTextValidator(validatorData) |
|
|
|
|
messages := []*NameValueType{ |
|
|
|
|
messages := []*apitypes.NameValueType{ |
|
|
|
|
{ |
|
|
|
|
Name: "This is a request to sign data intended for a particular validator (see EIP 191 version 0)", |
|
|
|
|
Typ: "description", |
|
|
|
@ -225,11 +133,11 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Messages: messages, Hash: sighash} |
|
|
|
|
case ApplicationClique.Mime: |
|
|
|
|
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", ApplicationClique.Mime) |
|
|
|
|
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 { |
|
|
|
@ -251,7 +159,7 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, useEthereumV, err |
|
|
|
|
} |
|
|
|
|
messages := []*NameValueType{ |
|
|
|
|
messages := []*apitypes.NameValueType{ |
|
|
|
|
{ |
|
|
|
|
Name: "Clique header", |
|
|
|
|
Typ: "clique", |
|
|
|
@ -272,7 +180,7 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType |
|
|
|
|
return nil, useEthereumV, err |
|
|
|
|
} else { |
|
|
|
|
sighash, msg := accounts.TextAndHash(textData) |
|
|
|
|
messages := []*NameValueType{ |
|
|
|
|
messages := []*apitypes.NameValueType{ |
|
|
|
|
{ |
|
|
|
|
Name: "message", |
|
|
|
|
Typ: accounts.MimetypeTextPlain, |
|
|
|
@ -291,7 +199,7 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType |
|
|
|
|
// SignTextWithValidator signs the given message which can be further recovered
|
|
|
|
|
// with the given validator.
|
|
|
|
|
// hash = keccak256("\x19\x00"${address}${data}).
|
|
|
|
|
func SignTextValidator(validatorData ValidatorData) (hexutil.Bytes, string) { |
|
|
|
|
func SignTextValidator(validatorData apitypes.ValidatorData) (hexutil.Bytes, string) { |
|
|
|
|
msg := fmt.Sprintf("\x19\x00%s%s", string(validatorData.Address.Bytes()), string(validatorData.Message)) |
|
|
|
|
return crypto.Keccak256([]byte(msg)), msg |
|
|
|
|
} |
|
|
|
@ -318,7 +226,7 @@ func cliqueHeaderHashAndRlp(header *types.Header) (hash, rlp []byte, err error) |
|
|
|
|
// It returns
|
|
|
|
|
// - the signature,
|
|
|
|
|
// - and/or any error
|
|
|
|
|
func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, typedData TypedData) (hexutil.Bytes, error) { |
|
|
|
|
func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, typedData apitypes.TypedData) (hexutil.Bytes, error) { |
|
|
|
|
signature, _, err := api.signTypedData(ctx, addr, typedData, nil) |
|
|
|
|
return signature, err |
|
|
|
|
} |
|
|
|
@ -326,7 +234,7 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd |
|
|
|
|
// signTypedData is identical to the capitalized version, except that it also returns the hash (preimage)
|
|
|
|
|
// - the signature preimage (hash)
|
|
|
|
|
func (api *SignerAPI) signTypedData(ctx context.Context, addr common.MixedcaseAddress, |
|
|
|
|
typedData TypedData, validationMessages *apitypes.ValidationMessages) (hexutil.Bytes, hexutil.Bytes, error) { |
|
|
|
|
typedData apitypes.TypedData, validationMessages *apitypes.ValidationMessages) (hexutil.Bytes, hexutil.Bytes, error) { |
|
|
|
|
domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, nil, err |
|
|
|
@ -342,7 +250,7 @@ func (api *SignerAPI) signTypedData(ctx context.Context, addr common.MixedcaseAd |
|
|
|
|
return nil, nil, err |
|
|
|
|
} |
|
|
|
|
req := &SignDataRequest{ |
|
|
|
|
ContentType: DataTyped.Mime, |
|
|
|
|
ContentType: apitypes.DataTyped.Mime, |
|
|
|
|
Rawdata: rawData, |
|
|
|
|
Messages: messages, |
|
|
|
|
Hash: sighash, |
|
|
|
@ -358,289 +266,6 @@ func (api *SignerAPI) signTypedData(ctx context.Context, addr common.MixedcaseAd |
|
|
|
|
return signature, sighash, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// HashStruct generates a keccak256 hash of the encoding of the provided data
|
|
|
|
|
func (typedData *TypedData) HashStruct(primaryType string, data TypedDataMessage) (hexutil.Bytes, error) { |
|
|
|
|
encodedData, err := typedData.EncodeData(primaryType, data, 1) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
return crypto.Keccak256(encodedData), nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Dependencies returns an array of custom types ordered by their hierarchical reference tree
|
|
|
|
|
func (typedData *TypedData) Dependencies(primaryType string, found []string) []string { |
|
|
|
|
includes := func(arr []string, str string) bool { |
|
|
|
|
for _, obj := range arr { |
|
|
|
|
if obj == str { |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if includes(found, primaryType) { |
|
|
|
|
return found |
|
|
|
|
} |
|
|
|
|
if typedData.Types[primaryType] == nil { |
|
|
|
|
return found |
|
|
|
|
} |
|
|
|
|
found = append(found, primaryType) |
|
|
|
|
for _, field := range typedData.Types[primaryType] { |
|
|
|
|
for _, dep := range typedData.Dependencies(field.Type, found) { |
|
|
|
|
if !includes(found, dep) { |
|
|
|
|
found = append(found, dep) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return found |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// EncodeType generates the following encoding:
|
|
|
|
|
// `name ‖ "(" ‖ member₁ ‖ "," ‖ member₂ ‖ "," ‖ … ‖ memberₙ ")"`
|
|
|
|
|
//
|
|
|
|
|
// each member is written as `type ‖ " " ‖ name` encodings cascade down and are sorted by name
|
|
|
|
|
func (typedData *TypedData) EncodeType(primaryType string) hexutil.Bytes { |
|
|
|
|
// Get dependencies primary first, then alphabetical
|
|
|
|
|
deps := typedData.Dependencies(primaryType, []string{}) |
|
|
|
|
if len(deps) > 0 { |
|
|
|
|
slicedDeps := deps[1:] |
|
|
|
|
sort.Strings(slicedDeps) |
|
|
|
|
deps = append([]string{primaryType}, slicedDeps...) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Format as a string with fields
|
|
|
|
|
var buffer bytes.Buffer |
|
|
|
|
for _, dep := range deps { |
|
|
|
|
buffer.WriteString(dep) |
|
|
|
|
buffer.WriteString("(") |
|
|
|
|
for _, obj := range typedData.Types[dep] { |
|
|
|
|
buffer.WriteString(obj.Type) |
|
|
|
|
buffer.WriteString(" ") |
|
|
|
|
buffer.WriteString(obj.Name) |
|
|
|
|
buffer.WriteString(",") |
|
|
|
|
} |
|
|
|
|
buffer.Truncate(buffer.Len() - 1) |
|
|
|
|
buffer.WriteString(")") |
|
|
|
|
} |
|
|
|
|
return buffer.Bytes() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// TypeHash creates the keccak256 hash of the data
|
|
|
|
|
func (typedData *TypedData) TypeHash(primaryType string) hexutil.Bytes { |
|
|
|
|
return crypto.Keccak256(typedData.EncodeType(primaryType)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// EncodeData generates the following encoding:
|
|
|
|
|
// `enc(value₁) ‖ enc(value₂) ‖ … ‖ enc(valueₙ)`
|
|
|
|
|
//
|
|
|
|
|
// each encoded member is 32-byte long
|
|
|
|
|
func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}, depth int) (hexutil.Bytes, error) { |
|
|
|
|
if err := typedData.validate(); err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
buffer := bytes.Buffer{} |
|
|
|
|
|
|
|
|
|
// Verify extra data
|
|
|
|
|
if exp, got := len(typedData.Types[primaryType]), len(data); exp < got { |
|
|
|
|
return nil, fmt.Errorf("there is extra data provided in the message (%d < %d)", exp, got) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Add typehash
|
|
|
|
|
buffer.Write(typedData.TypeHash(primaryType)) |
|
|
|
|
|
|
|
|
|
// Add field contents. Structs and arrays have special handlers.
|
|
|
|
|
for _, field := range typedData.Types[primaryType] { |
|
|
|
|
encType := field.Type |
|
|
|
|
encValue := data[field.Name] |
|
|
|
|
if encType[len(encType)-1:] == "]" { |
|
|
|
|
arrayValue, ok := encValue.([]interface{}) |
|
|
|
|
if !ok { |
|
|
|
|
return nil, dataMismatchError(encType, encValue) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
arrayBuffer := bytes.Buffer{} |
|
|
|
|
parsedType := strings.Split(encType, "[")[0] |
|
|
|
|
for _, item := range arrayValue { |
|
|
|
|
if typedData.Types[parsedType] != nil { |
|
|
|
|
mapValue, ok := item.(map[string]interface{}) |
|
|
|
|
if !ok { |
|
|
|
|
return nil, dataMismatchError(parsedType, item) |
|
|
|
|
} |
|
|
|
|
encodedData, err := typedData.EncodeData(parsedType, mapValue, depth+1) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
arrayBuffer.Write(encodedData) |
|
|
|
|
} else { |
|
|
|
|
bytesValue, err := typedData.EncodePrimitiveValue(parsedType, item, depth) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
arrayBuffer.Write(bytesValue) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
buffer.Write(crypto.Keccak256(arrayBuffer.Bytes())) |
|
|
|
|
} else if typedData.Types[field.Type] != nil { |
|
|
|
|
mapValue, ok := encValue.(map[string]interface{}) |
|
|
|
|
if !ok { |
|
|
|
|
return nil, dataMismatchError(encType, encValue) |
|
|
|
|
} |
|
|
|
|
encodedData, err := typedData.EncodeData(field.Type, mapValue, depth+1) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
buffer.Write(crypto.Keccak256(encodedData)) |
|
|
|
|
} else { |
|
|
|
|
byteValue, err := typedData.EncodePrimitiveValue(encType, encValue, depth) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
buffer.Write(byteValue) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return buffer.Bytes(), nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Attempt to parse bytes in different formats: byte array, hex string, hexutil.Bytes.
|
|
|
|
|
func parseBytes(encType interface{}) ([]byte, bool) { |
|
|
|
|
switch v := encType.(type) { |
|
|
|
|
case []byte: |
|
|
|
|
return v, true |
|
|
|
|
case hexutil.Bytes: |
|
|
|
|
return v, true |
|
|
|
|
case string: |
|
|
|
|
bytes, err := hexutil.Decode(v) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, false |
|
|
|
|
} |
|
|
|
|
return bytes, true |
|
|
|
|
default: |
|
|
|
|
return nil, false |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func parseInteger(encType string, encValue interface{}) (*big.Int, error) { |
|
|
|
|
var ( |
|
|
|
|
length int |
|
|
|
|
signed = strings.HasPrefix(encType, "int") |
|
|
|
|
b *big.Int |
|
|
|
|
) |
|
|
|
|
if encType == "int" || encType == "uint" { |
|
|
|
|
length = 256 |
|
|
|
|
} else { |
|
|
|
|
lengthStr := "" |
|
|
|
|
if strings.HasPrefix(encType, "uint") { |
|
|
|
|
lengthStr = strings.TrimPrefix(encType, "uint") |
|
|
|
|
} else { |
|
|
|
|
lengthStr = strings.TrimPrefix(encType, "int") |
|
|
|
|
} |
|
|
|
|
atoiSize, err := strconv.Atoi(lengthStr) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, fmt.Errorf("invalid size on integer: %v", lengthStr) |
|
|
|
|
} |
|
|
|
|
length = atoiSize |
|
|
|
|
} |
|
|
|
|
switch v := encValue.(type) { |
|
|
|
|
case *math.HexOrDecimal256: |
|
|
|
|
b = (*big.Int)(v) |
|
|
|
|
case string: |
|
|
|
|
var hexIntValue math.HexOrDecimal256 |
|
|
|
|
if err := hexIntValue.UnmarshalText([]byte(v)); err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
b = (*big.Int)(&hexIntValue) |
|
|
|
|
case float64: |
|
|
|
|
// JSON parses non-strings as float64. Fail if we cannot
|
|
|
|
|
// convert it losslessly
|
|
|
|
|
if float64(int64(v)) == v { |
|
|
|
|
b = big.NewInt(int64(v)) |
|
|
|
|
} else { |
|
|
|
|
return nil, fmt.Errorf("invalid float value %v for type %v", v, encType) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if b == nil { |
|
|
|
|
return nil, fmt.Errorf("invalid integer value %v/%v for type %v", encValue, reflect.TypeOf(encValue), encType) |
|
|
|
|
} |
|
|
|
|
if b.BitLen() > length { |
|
|
|
|
return nil, fmt.Errorf("integer larger than '%v'", encType) |
|
|
|
|
} |
|
|
|
|
if !signed && b.Sign() == -1 { |
|
|
|
|
return nil, fmt.Errorf("invalid negative value for unsigned type %v", encType) |
|
|
|
|
} |
|
|
|
|
return b, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// EncodePrimitiveValue deals with the primitive values found
|
|
|
|
|
// while searching through the typed data
|
|
|
|
|
func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interface{}, depth int) ([]byte, error) { |
|
|
|
|
switch encType { |
|
|
|
|
case "address": |
|
|
|
|
stringValue, ok := encValue.(string) |
|
|
|
|
if !ok || !common.IsHexAddress(stringValue) { |
|
|
|
|
return nil, dataMismatchError(encType, encValue) |
|
|
|
|
} |
|
|
|
|
retval := make([]byte, 32) |
|
|
|
|
copy(retval[12:], common.HexToAddress(stringValue).Bytes()) |
|
|
|
|
return retval, nil |
|
|
|
|
case "bool": |
|
|
|
|
boolValue, ok := encValue.(bool) |
|
|
|
|
if !ok { |
|
|
|
|
return nil, dataMismatchError(encType, encValue) |
|
|
|
|
} |
|
|
|
|
if boolValue { |
|
|
|
|
return math.PaddedBigBytes(common.Big1, 32), nil |
|
|
|
|
} |
|
|
|
|
return math.PaddedBigBytes(common.Big0, 32), nil |
|
|
|
|
case "string": |
|
|
|
|
strVal, ok := encValue.(string) |
|
|
|
|
if !ok { |
|
|
|
|
return nil, dataMismatchError(encType, encValue) |
|
|
|
|
} |
|
|
|
|
return crypto.Keccak256([]byte(strVal)), nil |
|
|
|
|
case "bytes": |
|
|
|
|
bytesValue, ok := parseBytes(encValue) |
|
|
|
|
if !ok { |
|
|
|
|
return nil, dataMismatchError(encType, encValue) |
|
|
|
|
} |
|
|
|
|
return crypto.Keccak256(bytesValue), nil |
|
|
|
|
} |
|
|
|
|
if strings.HasPrefix(encType, "bytes") { |
|
|
|
|
lengthStr := strings.TrimPrefix(encType, "bytes") |
|
|
|
|
length, err := strconv.Atoi(lengthStr) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, fmt.Errorf("invalid size on bytes: %v", lengthStr) |
|
|
|
|
} |
|
|
|
|
if length < 0 || length > 32 { |
|
|
|
|
return nil, fmt.Errorf("invalid size on bytes: %d", length) |
|
|
|
|
} |
|
|
|
|
if byteValue, ok := parseBytes(encValue); !ok || len(byteValue) != length { |
|
|
|
|
return nil, dataMismatchError(encType, encValue) |
|
|
|
|
} else { |
|
|
|
|
// Right-pad the bits
|
|
|
|
|
dst := make([]byte, 32) |
|
|
|
|
copy(dst, byteValue) |
|
|
|
|
return dst, nil |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if strings.HasPrefix(encType, "int") || strings.HasPrefix(encType, "uint") { |
|
|
|
|
b, err := parseInteger(encType, encValue) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
return math.U256Bytes(b), nil |
|
|
|
|
} |
|
|
|
|
return nil, fmt.Errorf("unrecognized type '%s'", encType) |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// dataMismatchError generates an error for a mismatch between
|
|
|
|
|
// the provided type and data
|
|
|
|
|
func dataMismatchError(encType string, encValue interface{}) error { |
|
|
|
|
return fmt.Errorf("provided data '%v' doesn't match type '%s'", encValue, encType) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// EcRecover recovers the address associated with the given sig.
|
|
|
|
|
// Only compatible with `text/plain`
|
|
|
|
|
func (api *SignerAPI) EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { |
|
|
|
@ -671,376 +296,37 @@ func (api *SignerAPI) EcRecover(ctx context.Context, data hexutil.Bytes, sig hex |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// UnmarshalValidatorData converts the bytes input to typed data
|
|
|
|
|
func UnmarshalValidatorData(data interface{}) (ValidatorData, error) { |
|
|
|
|
func UnmarshalValidatorData(data interface{}) (apitypes.ValidatorData, error) { |
|
|
|
|
raw, ok := data.(map[string]interface{}) |
|
|
|
|
if !ok { |
|
|
|
|
return 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) |
|
|
|
|
if !ok { |
|
|
|
|
return ValidatorData{}, errors.New("validator address is not sent as a string") |
|
|
|
|
return apitypes.ValidatorData{}, errors.New("validator address is not sent as a string") |
|
|
|
|
} |
|
|
|
|
addrBytes, err := hexutil.Decode(addr) |
|
|
|
|
if err != nil { |
|
|
|
|
return ValidatorData{}, err |
|
|
|
|
return apitypes.ValidatorData{}, err |
|
|
|
|
} |
|
|
|
|
if !ok || len(addrBytes) == 0 { |
|
|
|
|
return ValidatorData{}, errors.New("validator address is undefined") |
|
|
|
|
return apitypes.ValidatorData{}, errors.New("validator address is undefined") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
message, ok := raw["message"].(string) |
|
|
|
|
if !ok { |
|
|
|
|
return ValidatorData{}, errors.New("message is not sent as a string") |
|
|
|
|
return apitypes.ValidatorData{}, errors.New("message is not sent as a string") |
|
|
|
|
} |
|
|
|
|
messageBytes, err := hexutil.Decode(message) |
|
|
|
|
if err != nil { |
|
|
|
|
return ValidatorData{}, err |
|
|
|
|
return apitypes.ValidatorData{}, err |
|
|
|
|
} |
|
|
|
|
if !ok || len(messageBytes) == 0 { |
|
|
|
|
return ValidatorData{}, errors.New("message is undefined") |
|
|
|
|
return apitypes.ValidatorData{}, errors.New("message is undefined") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return ValidatorData{ |
|
|
|
|
return apitypes.ValidatorData{ |
|
|
|
|
Address: common.BytesToAddress(addrBytes), |
|
|
|
|
Message: messageBytes, |
|
|
|
|
}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// validate makes sure the types are sound
|
|
|
|
|
func (typedData *TypedData) validate() error { |
|
|
|
|
if err := typedData.Types.validate(); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
if err := typedData.Domain.validate(); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Map generates a map version of the typed data
|
|
|
|
|
func (typedData *TypedData) Map() map[string]interface{} { |
|
|
|
|
dataMap := map[string]interface{}{ |
|
|
|
|
"types": typedData.Types, |
|
|
|
|
"domain": typedData.Domain.Map(), |
|
|
|
|
"primaryType": typedData.PrimaryType, |
|
|
|
|
"message": typedData.Message, |
|
|
|
|
} |
|
|
|
|
return dataMap |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Format returns a representation of typedData, which can be easily displayed by a user-interface
|
|
|
|
|
// without in-depth knowledge about 712 rules
|
|
|
|
|
func (typedData *TypedData) Format() ([]*NameValueType, error) { |
|
|
|
|
domain, err := typedData.formatData("EIP712Domain", typedData.Domain.Map()) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
ptype, err := typedData.formatData(typedData.PrimaryType, typedData.Message) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
var nvts []*NameValueType |
|
|
|
|
nvts = append(nvts, &NameValueType{ |
|
|
|
|
Name: "EIP712Domain", |
|
|
|
|
Value: domain, |
|
|
|
|
Typ: "domain", |
|
|
|
|
}) |
|
|
|
|
nvts = append(nvts, &NameValueType{ |
|
|
|
|
Name: typedData.PrimaryType, |
|
|
|
|
Value: ptype, |
|
|
|
|
Typ: "primary type", |
|
|
|
|
}) |
|
|
|
|
return nvts, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (typedData *TypedData) formatData(primaryType string, data map[string]interface{}) ([]*NameValueType, error) { |
|
|
|
|
var output []*NameValueType |
|
|
|
|
|
|
|
|
|
// Add field contents. Structs and arrays have special handlers.
|
|
|
|
|
for _, field := range typedData.Types[primaryType] { |
|
|
|
|
encName := field.Name |
|
|
|
|
encValue := data[encName] |
|
|
|
|
item := &NameValueType{ |
|
|
|
|
Name: encName, |
|
|
|
|
Typ: field.Type, |
|
|
|
|
} |
|
|
|
|
if field.isArray() { |
|
|
|
|
arrayValue, _ := encValue.([]interface{}) |
|
|
|
|
parsedType := field.typeName() |
|
|
|
|
for _, v := range arrayValue { |
|
|
|
|
if typedData.Types[parsedType] != nil { |
|
|
|
|
mapValue, _ := v.(map[string]interface{}) |
|
|
|
|
mapOutput, err := typedData.formatData(parsedType, mapValue) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
item.Value = mapOutput |
|
|
|
|
} else { |
|
|
|
|
primitiveOutput, err := formatPrimitiveValue(field.Type, encValue) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
item.Value = primitiveOutput |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} else if typedData.Types[field.Type] != nil { |
|
|
|
|
if mapValue, ok := encValue.(map[string]interface{}); ok { |
|
|
|
|
mapOutput, err := typedData.formatData(field.Type, mapValue) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
item.Value = mapOutput |
|
|
|
|
} else { |
|
|
|
|
item.Value = "<nil>" |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
primitiveOutput, err := formatPrimitiveValue(field.Type, encValue) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
item.Value = primitiveOutput |
|
|
|
|
} |
|
|
|
|
output = append(output, item) |
|
|
|
|
} |
|
|
|
|
return output, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func formatPrimitiveValue(encType string, encValue interface{}) (string, error) { |
|
|
|
|
switch encType { |
|
|
|
|
case "address": |
|
|
|
|
if stringValue, ok := encValue.(string); !ok { |
|
|
|
|
return "", fmt.Errorf("could not format value %v as address", encValue) |
|
|
|
|
} else { |
|
|
|
|
return common.HexToAddress(stringValue).String(), nil |
|
|
|
|
} |
|
|
|
|
case "bool": |
|
|
|
|
if boolValue, ok := encValue.(bool); !ok { |
|
|
|
|
return "", fmt.Errorf("could not format value %v as bool", encValue) |
|
|
|
|
} else { |
|
|
|
|
return fmt.Sprintf("%t", boolValue), nil |
|
|
|
|
} |
|
|
|
|
case "bytes", "string": |
|
|
|
|
return fmt.Sprintf("%s", encValue), nil |
|
|
|
|
} |
|
|
|
|
if strings.HasPrefix(encType, "bytes") { |
|
|
|
|
return fmt.Sprintf("%s", encValue), nil |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { |
|
|
|
|
if b, err := parseInteger(encType, encValue); err != nil { |
|
|
|
|
return "", err |
|
|
|
|
} else { |
|
|
|
|
return fmt.Sprintf("%d (0x%x)", b, b), nil |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return "", fmt.Errorf("unhandled type %v", encType) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// NameValueType is a very simple struct with Name, Value and Type. It's meant for simple
|
|
|
|
|
// json structures used to communicate signing-info about typed data with the UI
|
|
|
|
|
type NameValueType struct { |
|
|
|
|
Name string `json:"name"` |
|
|
|
|
Value interface{} `json:"value"` |
|
|
|
|
Typ string `json:"type"` |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Pprint returns a pretty-printed version of nvt
|
|
|
|
|
func (nvt *NameValueType) Pprint(depth int) string { |
|
|
|
|
output := bytes.Buffer{} |
|
|
|
|
output.WriteString(strings.Repeat("\u00a0", depth*2)) |
|
|
|
|
output.WriteString(fmt.Sprintf("%s [%s]: ", nvt.Name, nvt.Typ)) |
|
|
|
|
if nvts, ok := nvt.Value.([]*NameValueType); ok { |
|
|
|
|
output.WriteString("\n") |
|
|
|
|
for _, next := range nvts { |
|
|
|
|
sublevel := next.Pprint(depth + 1) |
|
|
|
|
output.WriteString(sublevel) |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
if nvt.Value != nil { |
|
|
|
|
output.WriteString(fmt.Sprintf("%q\n", nvt.Value)) |
|
|
|
|
} else { |
|
|
|
|
output.WriteString("\n") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return output.String() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Validate checks if the types object is conformant to the specs
|
|
|
|
|
func (t Types) validate() error { |
|
|
|
|
for typeKey, typeArr := range t { |
|
|
|
|
if len(typeKey) == 0 { |
|
|
|
|
return fmt.Errorf("empty type key") |
|
|
|
|
} |
|
|
|
|
for i, typeObj := range typeArr { |
|
|
|
|
if len(typeObj.Type) == 0 { |
|
|
|
|
return fmt.Errorf("type %q:%d: empty Type", typeKey, i) |
|
|
|
|
} |
|
|
|
|
if len(typeObj.Name) == 0 { |
|
|
|
|
return fmt.Errorf("type %q:%d: empty Name", typeKey, i) |
|
|
|
|
} |
|
|
|
|
if typeKey == typeObj.Type { |
|
|
|
|
return fmt.Errorf("type %q cannot reference itself", typeObj.Type) |
|
|
|
|
} |
|
|
|
|
if typeObj.isReferenceType() { |
|
|
|
|
if _, exist := t[typeObj.typeName()]; !exist { |
|
|
|
|
return fmt.Errorf("reference type %q is undefined", typeObj.Type) |
|
|
|
|
} |
|
|
|
|
if !typedDataReferenceTypeRegexp.MatchString(typeObj.Type) { |
|
|
|
|
return fmt.Errorf("unknown reference type %q", typeObj.Type) |
|
|
|
|
} |
|
|
|
|
} else if !isPrimitiveTypeValid(typeObj.Type) { |
|
|
|
|
return fmt.Errorf("unknown type %q", typeObj.Type) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Checks if the primitive value is valid
|
|
|
|
|
func isPrimitiveTypeValid(primitiveType string) bool { |
|
|
|
|
if primitiveType == "address" || |
|
|
|
|
primitiveType == "address[]" || |
|
|
|
|
primitiveType == "bool" || |
|
|
|
|
primitiveType == "bool[]" || |
|
|
|
|
primitiveType == "string" || |
|
|
|
|
primitiveType == "string[]" { |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
if primitiveType == "bytes" || |
|
|
|
|
primitiveType == "bytes[]" || |
|
|
|
|
primitiveType == "bytes1" || |
|
|
|
|
primitiveType == "bytes1[]" || |
|
|
|
|
primitiveType == "bytes2" || |
|
|
|
|
primitiveType == "bytes2[]" || |
|
|
|
|
primitiveType == "bytes3" || |
|
|
|
|
primitiveType == "bytes3[]" || |
|
|
|
|
primitiveType == "bytes4" || |
|
|
|
|
primitiveType == "bytes4[]" || |
|
|
|
|
primitiveType == "bytes5" || |
|
|
|
|
primitiveType == "bytes5[]" || |
|
|
|
|
primitiveType == "bytes6" || |
|
|
|
|
primitiveType == "bytes6[]" || |
|
|
|
|
primitiveType == "bytes7" || |
|
|
|
|
primitiveType == "bytes7[]" || |
|
|
|
|
primitiveType == "bytes8" || |
|
|
|
|
primitiveType == "bytes8[]" || |
|
|
|
|
primitiveType == "bytes9" || |
|
|
|
|
primitiveType == "bytes9[]" || |
|
|
|
|
primitiveType == "bytes10" || |
|
|
|
|
primitiveType == "bytes10[]" || |
|
|
|
|
primitiveType == "bytes11" || |
|
|
|
|
primitiveType == "bytes11[]" || |
|
|
|
|
primitiveType == "bytes12" || |
|
|
|
|
primitiveType == "bytes12[]" || |
|
|
|
|
primitiveType == "bytes13" || |
|
|
|
|
primitiveType == "bytes13[]" || |
|
|
|
|
primitiveType == "bytes14" || |
|
|
|
|
primitiveType == "bytes14[]" || |
|
|
|
|
primitiveType == "bytes15" || |
|
|
|
|
primitiveType == "bytes15[]" || |
|
|
|
|
primitiveType == "bytes16" || |
|
|
|
|
primitiveType == "bytes16[]" || |
|
|
|
|
primitiveType == "bytes17" || |
|
|
|
|
primitiveType == "bytes17[]" || |
|
|
|
|
primitiveType == "bytes18" || |
|
|
|
|
primitiveType == "bytes18[]" || |
|
|
|
|
primitiveType == "bytes19" || |
|
|
|
|
primitiveType == "bytes19[]" || |
|
|
|
|
primitiveType == "bytes20" || |
|
|
|
|
primitiveType == "bytes20[]" || |
|
|
|
|
primitiveType == "bytes21" || |
|
|
|
|
primitiveType == "bytes21[]" || |
|
|
|
|
primitiveType == "bytes22" || |
|
|
|
|
primitiveType == "bytes22[]" || |
|
|
|
|
primitiveType == "bytes23" || |
|
|
|
|
primitiveType == "bytes23[]" || |
|
|
|
|
primitiveType == "bytes24" || |
|
|
|
|
primitiveType == "bytes24[]" || |
|
|
|
|
primitiveType == "bytes25" || |
|
|
|
|
primitiveType == "bytes25[]" || |
|
|
|
|
primitiveType == "bytes26" || |
|
|
|
|
primitiveType == "bytes26[]" || |
|
|
|
|
primitiveType == "bytes27" || |
|
|
|
|
primitiveType == "bytes27[]" || |
|
|
|
|
primitiveType == "bytes28" || |
|
|
|
|
primitiveType == "bytes28[]" || |
|
|
|
|
primitiveType == "bytes29" || |
|
|
|
|
primitiveType == "bytes29[]" || |
|
|
|
|
primitiveType == "bytes30" || |
|
|
|
|
primitiveType == "bytes30[]" || |
|
|
|
|
primitiveType == "bytes31" || |
|
|
|
|
primitiveType == "bytes31[]" || |
|
|
|
|
primitiveType == "bytes32" || |
|
|
|
|
primitiveType == "bytes32[]" { |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
if primitiveType == "int" || |
|
|
|
|
primitiveType == "int[]" || |
|
|
|
|
primitiveType == "int8" || |
|
|
|
|
primitiveType == "int8[]" || |
|
|
|
|
primitiveType == "int16" || |
|
|
|
|
primitiveType == "int16[]" || |
|
|
|
|
primitiveType == "int32" || |
|
|
|
|
primitiveType == "int32[]" || |
|
|
|
|
primitiveType == "int64" || |
|
|
|
|
primitiveType == "int64[]" || |
|
|
|
|
primitiveType == "int128" || |
|
|
|
|
primitiveType == "int128[]" || |
|
|
|
|
primitiveType == "int256" || |
|
|
|
|
primitiveType == "int256[]" { |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
if primitiveType == "uint" || |
|
|
|
|
primitiveType == "uint[]" || |
|
|
|
|
primitiveType == "uint8" || |
|
|
|
|
primitiveType == "uint8[]" || |
|
|
|
|
primitiveType == "uint16" || |
|
|
|
|
primitiveType == "uint16[]" || |
|
|
|
|
primitiveType == "uint32" || |
|
|
|
|
primitiveType == "uint32[]" || |
|
|
|
|
primitiveType == "uint64" || |
|
|
|
|
primitiveType == "uint64[]" || |
|
|
|
|
primitiveType == "uint128" || |
|
|
|
|
primitiveType == "uint128[]" || |
|
|
|
|
primitiveType == "uint256" || |
|
|
|
|
primitiveType == "uint256[]" { |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// validate checks if the given domain is valid, i.e. contains at least
|
|
|
|
|
// the minimum viable keys and values
|
|
|
|
|
func (domain *TypedDataDomain) validate() error { |
|
|
|
|
if domain.ChainId == nil && len(domain.Name) == 0 && len(domain.Version) == 0 && len(domain.VerifyingContract) == 0 && len(domain.Salt) == 0 { |
|
|
|
|
return errors.New("domain is undefined") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Map is a helper function to generate a map version of the domain
|
|
|
|
|
func (domain *TypedDataDomain) Map() map[string]interface{} { |
|
|
|
|
dataMap := map[string]interface{}{} |
|
|
|
|
|
|
|
|
|
if domain.ChainId != nil { |
|
|
|
|
dataMap["chainId"] = domain.ChainId |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if len(domain.Name) > 0 { |
|
|
|
|
dataMap["name"] = domain.Name |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if len(domain.Version) > 0 { |
|
|
|
|
dataMap["version"] = domain.Version |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if len(domain.VerifyingContract) > 0 { |
|
|
|
|
dataMap["verifyingContract"] = domain.VerifyingContract |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if len(domain.Salt) > 0 { |
|
|
|
|
dataMap["salt"] = domain.Salt |
|
|
|
|
} |
|
|
|
|
return dataMap |
|
|
|
|
} |
|
|
|
|