signer/core: move EIP-712 types to package apitypes (#24029)
Fixes #23972
This commit is contained in:
parent
93f196c4b0
commit
619a3e7085
@ -898,7 +898,7 @@ func testExternalUI(api *core.SignerAPI) {
|
||||
addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
|
||||
data := `{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Person":[{"name":"name","type":"string"},{"name":"test","type":"uint8"},{"name":"wallet","type":"address"}],"Mail":[{"name":"from","type":"Person"},{"name":"to","type":"Person"},{"name":"contents","type":"string"}]},"primaryType":"Mail","domain":{"name":"Ether Mail","version":"1","chainId":"1","verifyingContract":"0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"},"message":{"from":{"name":"Cow","test":"3","wallet":"0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"},"to":{"name":"Bob","wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB","test":"2"},"contents":"Hello, Bob!"}}`
|
||||
//_, err := api.SignData(ctx, accounts.MimetypeTypedData, *addr, hexutil.Encode([]byte(data)))
|
||||
var typedData core.TypedData
|
||||
var typedData apitypes.TypedData
|
||||
json.Unmarshal([]byte(data), &typedData)
|
||||
_, err := api.SignTypedData(ctx, *addr, typedData)
|
||||
expectApprove("sign 712 typed data", err)
|
||||
@ -1025,7 +1025,7 @@ func GenDoc(ctx *cli.Context) {
|
||||
"of the work in canonicalizing and making sense of the data, and it's up to the UI to present" +
|
||||
"the user with the contents of the `message`"
|
||||
sighash, msg := accounts.TextAndHash([]byte("hello world"))
|
||||
messages := []*core.NameValueType{{Name: "message", Value: msg, Typ: accounts.MimetypeTextPlain}}
|
||||
messages := []*apitypes.NameValueType{{Name: "message", Value: msg, Typ: accounts.MimetypeTextPlain}}
|
||||
|
||||
add("SignDataRequest", desc, &core.SignDataRequest{
|
||||
Address: common.NewMixedcaseAddress(a),
|
||||
|
@ -57,7 +57,7 @@ type ExternalAPI interface {
|
||||
// SignData - request to sign the given data (plus prefix)
|
||||
SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error)
|
||||
// SignTypedData - request to sign the given structured data (plus prefix)
|
||||
SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error)
|
||||
SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data apitypes.TypedData) (hexutil.Bytes, error)
|
||||
// EcRecover - recover public key from given message and signature
|
||||
EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error)
|
||||
// Version info about the APIs
|
||||
@ -235,7 +235,7 @@ type (
|
||||
ContentType string `json:"content_type"`
|
||||
Address common.MixedcaseAddress `json:"address"`
|
||||
Rawdata []byte `json:"raw_data"`
|
||||
Messages []*NameValueType `json:"messages"`
|
||||
Messages []*apitypes.NameValueType `json:"messages"`
|
||||
Callinfo []apitypes.ValidationInfo `json:"call_info"`
|
||||
Hash hexutil.Bytes `json:"hash"`
|
||||
Meta Metadata `json:"meta"`
|
||||
|
@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
package apitypes
|
||||
|
||||
import (
|
||||
"bytes"
|
@ -17,16 +17,29 @@
|
||||
package apitypes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"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/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
var typedDataReferenceTypeRegexp = regexp.MustCompile(`^[A-Z](\w*)(\[\])?$`)
|
||||
|
||||
type ValidationInfo struct {
|
||||
Typ string `json:"type"`
|
||||
Message string `json:"message"`
|
||||
@ -154,3 +167,708 @@ func (args *SendTxArgs) ToTransaction() *types.Transaction {
|
||||
}
|
||||
return types.NewTx(data)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// TypedData is a type to encapsulate EIP-712 typed messages
|
||||
type TypedData struct {
|
||||
Types Types `json:"types"`
|
||||
PrimaryType string `json:"primaryType"`
|
||||
Domain TypedDataDomain `json:"domain"`
|
||||
Message TypedDataMessage `json:"message"`
|
||||
}
|
||||
|
||||
// Type is the inner type of an EIP-712 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{}
|
||||
|
||||
// TypedDataDomain represents the domain part of an EIP-712 message.
|
||||
type TypedDataDomain struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
ChainId *math.HexOrDecimal256 `json:"chainId"`
|
||||
VerifyingContract string `json:"verifyingContract"`
|
||||
Salt string `json:"salt"`
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ func (l *AuditLogger) SignGnosisSafeTx(ctx context.Context, addr common.Mixedcas
|
||||
return res, e
|
||||
}
|
||||
|
||||
func (l *AuditLogger) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) {
|
||||
func (l *AuditLogger) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data apitypes.TypedData) (hexutil.Bytes, error) {
|
||||
l.log.Info("SignTypedData", "type", "request", "metadata", MetadataFromContext(ctx).String(),
|
||||
"addr", addr.String(), "data", data)
|
||||
b, e := l.api.SignTypedData(ctx, addr, data)
|
||||
|
@ -34,15 +34,15 @@ type GnosisSafeTx struct {
|
||||
}
|
||||
|
||||
// ToTypedData converts the tx to a EIP-712 Typed Data structure for signing
|
||||
func (tx *GnosisSafeTx) ToTypedData() TypedData {
|
||||
func (tx *GnosisSafeTx) ToTypedData() apitypes.TypedData {
|
||||
var data hexutil.Bytes
|
||||
if tx.Data != nil {
|
||||
data = *tx.Data
|
||||
}
|
||||
gnosisTypedData := TypedData{
|
||||
Types: Types{
|
||||
"EIP712Domain": []Type{{Name: "verifyingContract", Type: "address"}},
|
||||
"SafeTx": []Type{
|
||||
gnosisTypedData := apitypes.TypedData{
|
||||
Types: apitypes.Types{
|
||||
"EIP712Domain": []apitypes.Type{{Name: "verifyingContract", Type: "address"}},
|
||||
"SafeTx": []apitypes.Type{
|
||||
{Name: "to", Type: "address"},
|
||||
{Name: "value", Type: "uint256"},
|
||||
{Name: "data", Type: "bytes"},
|
||||
@ -55,11 +55,11 @@ func (tx *GnosisSafeTx) ToTypedData() TypedData {
|
||||
{Name: "nonce", Type: "uint256"},
|
||||
},
|
||||
},
|
||||
Domain: TypedDataDomain{
|
||||
Domain: apitypes.TypedDataDomain{
|
||||
VerifyingContract: tx.Safe.Address().Hex(),
|
||||
},
|
||||
PrimaryType: "SafeTx",
|
||||
Message: TypedDataMessage{
|
||||
Message: apitypes.TypedDataMessage{
|
||||
"to": tx.To.Address().Hex(),
|
||||
"value": tx.Value.String(),
|
||||
"data": data,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -32,9 +32,10 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/signer/core"
|
||||
"github.com/ethereum/go-ethereum/signer/core/apitypes"
|
||||
)
|
||||
|
||||
var typesStandard = core.Types{
|
||||
var typesStandard = apitypes.Types{
|
||||
"EIP712Domain": {
|
||||
{
|
||||
Name: "name",
|
||||
@ -153,12 +154,12 @@ var jsonTypedData = `
|
||||
|
||||
const primaryType = "Mail"
|
||||
|
||||
var domainStandard = core.TypedDataDomain{
|
||||
"Ether Mail",
|
||||
"1",
|
||||
math.NewHexOrDecimal256(1),
|
||||
"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
|
||||
"",
|
||||
var domainStandard = apitypes.TypedDataDomain{
|
||||
Name: "Ether Mail",
|
||||
Version: "1",
|
||||
ChainId: math.NewHexOrDecimal256(1),
|
||||
VerifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
|
||||
Salt: "",
|
||||
}
|
||||
|
||||
var messageStandard = map[string]interface{}{
|
||||
@ -173,7 +174,7 @@ var messageStandard = map[string]interface{}{
|
||||
"contents": "Hello, Bob!",
|
||||
}
|
||||
|
||||
var typedData = core.TypedData{
|
||||
var typedData = apitypes.TypedData{
|
||||
Types: typesStandard,
|
||||
PrimaryType: primaryType,
|
||||
Domain: domainStandard,
|
||||
@ -194,7 +195,7 @@ func TestSignData(t *testing.T) {
|
||||
|
||||
control.approveCh <- "Y"
|
||||
control.inputCh <- "wrongpassword"
|
||||
signature, err := api.SignData(context.Background(), core.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
|
||||
signature, err := api.SignData(context.Background(), apitypes.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
|
||||
if signature != nil {
|
||||
t.Errorf("Expected nil-data, got %x", signature)
|
||||
}
|
||||
@ -202,7 +203,7 @@ func TestSignData(t *testing.T) {
|
||||
t.Errorf("Expected ErrLocked! '%v'", err)
|
||||
}
|
||||
control.approveCh <- "No way"
|
||||
signature, err = api.SignData(context.Background(), core.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
|
||||
signature, err = api.SignData(context.Background(), apitypes.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
|
||||
if signature != nil {
|
||||
t.Errorf("Expected nil-data, got %x", signature)
|
||||
}
|
||||
@ -212,7 +213,7 @@ func TestSignData(t *testing.T) {
|
||||
// text/plain
|
||||
control.approveCh <- "Y"
|
||||
control.inputCh <- "a_long_password"
|
||||
signature, err = api.SignData(context.Background(), core.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
|
||||
signature, err = api.SignData(context.Background(), apitypes.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -232,13 +233,13 @@ func TestSignData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDomainChainId(t *testing.T) {
|
||||
withoutChainID := core.TypedData{
|
||||
Types: core.Types{
|
||||
"EIP712Domain": []core.Type{
|
||||
withoutChainID := apitypes.TypedData{
|
||||
Types: apitypes.Types{
|
||||
"EIP712Domain": []apitypes.Type{
|
||||
{Name: "name", Type: "string"},
|
||||
},
|
||||
},
|
||||
Domain: core.TypedDataDomain{
|
||||
Domain: apitypes.TypedDataDomain{
|
||||
Name: "test",
|
||||
},
|
||||
}
|
||||
@ -250,14 +251,14 @@ func TestDomainChainId(t *testing.T) {
|
||||
if _, err := withoutChainID.HashStruct("EIP712Domain", withoutChainID.Domain.Map()); err != nil {
|
||||
t.Errorf("Expected the typedData to encode the domain successfully, got %v", err)
|
||||
}
|
||||
withChainID := core.TypedData{
|
||||
Types: core.Types{
|
||||
"EIP712Domain": []core.Type{
|
||||
withChainID := apitypes.TypedData{
|
||||
Types: apitypes.Types{
|
||||
"EIP712Domain": []apitypes.Type{
|
||||
{Name: "name", Type: "string"},
|
||||
{Name: "chainId", Type: "uint256"},
|
||||
},
|
||||
},
|
||||
Domain: core.TypedDataDomain{
|
||||
Domain: apitypes.TypedDataDomain{
|
||||
Name: "test",
|
||||
ChainId: math.NewHexOrDecimal256(1),
|
||||
},
|
||||
@ -323,7 +324,7 @@ func TestEncodeData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFormatter(t *testing.T) {
|
||||
var d core.TypedData
|
||||
var d apitypes.TypedData
|
||||
err := json.Unmarshal([]byte(jsonTypedData), &d)
|
||||
if err != nil {
|
||||
t.Fatalf("unmarshalling failed '%v'", err)
|
||||
@ -337,7 +338,7 @@ func TestFormatter(t *testing.T) {
|
||||
t.Logf("'%v'\n", string(j))
|
||||
}
|
||||
|
||||
func sign(typedData core.TypedData) ([]byte, []byte, error) {
|
||||
func sign(typedData apitypes.TypedData) ([]byte, []byte, error) {
|
||||
domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@ -366,7 +367,7 @@ func TestJsonFiles(t *testing.T) {
|
||||
t.Errorf("Failed to read file %v: %v", fInfo.Name(), err)
|
||||
continue
|
||||
}
|
||||
var typedData core.TypedData
|
||||
var typedData apitypes.TypedData
|
||||
err = json.Unmarshal(data, &typedData)
|
||||
if err != nil {
|
||||
t.Errorf("Test %d, file %v, json unmarshalling failed: %v", i, fInfo.Name(), err)
|
||||
@ -398,7 +399,7 @@ func TestFuzzerFiles(t *testing.T) {
|
||||
t.Errorf("Failed to read file %v: %v", fInfo.Name(), err)
|
||||
continue
|
||||
}
|
||||
var typedData core.TypedData
|
||||
var typedData apitypes.TypedData
|
||||
err = json.Unmarshal(data, &typedData)
|
||||
if err != nil {
|
||||
t.Errorf("Test %d, file %v, json unmarshalling failed: %v", i, fInfo.Name(), err)
|
||||
@ -498,7 +499,7 @@ var gnosisTx = `
|
||||
// TestGnosisTypedData tests the scenario where a user submits a full EIP-712
|
||||
// struct without using the gnosis-specific endpoint
|
||||
func TestGnosisTypedData(t *testing.T) {
|
||||
var td core.TypedData
|
||||
var td apitypes.TypedData
|
||||
err := json.Unmarshal([]byte(gnosisTypedData), &td)
|
||||
if err != nil {
|
||||
t.Fatalf("unmarshalling failed '%v'", err)
|
||||
|
@ -605,7 +605,7 @@ function ApproveSignData(r){
|
||||
|
||||
t.Logf("address %v %v\n", addr.String(), addr.Original())
|
||||
|
||||
nvt := []*core.NameValueType{
|
||||
nvt := []*apitypes.NameValueType{
|
||||
{
|
||||
Name: "message",
|
||||
Typ: "text/plain",
|
||||
|
Loading…
Reference in New Issue
Block a user