Commit 17381ecc authored by Martin Holst Swende's avatar Martin Holst Swende Committed by Guillaume Ballet

core/signer, clef: improve ui-test flow, fix errors in uint handling (#19584)

* core/signer, clef: improve ui-test flow, fix errors in uint handling for eip-712

* core/signer: add fuzzer testcases + crashfixes

* signer: address review concerns, check sign in integer parsing
parent 7a22da98
...@@ -33,6 +33,7 @@ import ( ...@@ -33,6 +33,7 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"time"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/accounts/keystore"
...@@ -638,6 +639,10 @@ func testExternalUI(api *core.SignerAPI) { ...@@ -638,6 +639,10 @@ func testExternalUI(api *core.SignerAPI) {
errs := make([]string, 0) errs := make([]string, 0)
a := common.HexToAddress("0xdeadbeef000000000000000000000000deadbeef") a := common.HexToAddress("0xdeadbeef000000000000000000000000deadbeef")
addErr := func(errStr string) {
log.Info("Test error", "error", errStr)
errs = append(errs, errStr)
}
queryUser := func(q string) string { queryUser := func(q string) string {
resp, err := api.UI.OnInputRequired(core.UserInputRequest{ resp, err := api.UI.OnInputRequired(core.UserInputRequest{
...@@ -645,36 +650,39 @@ func testExternalUI(api *core.SignerAPI) { ...@@ -645,36 +650,39 @@ func testExternalUI(api *core.SignerAPI) {
Prompt: q, Prompt: q,
}) })
if err != nil { if err != nil {
errs = append(errs, err.Error()) addErr(err.Error())
} }
return resp.Text return resp.Text
} }
expectResponse := func(testcase, question, expect string) { expectResponse := func(testcase, question, expect string) {
if got := queryUser(question); got != expect { if got := queryUser(question); got != expect {
errs = append(errs, fmt.Sprintf("%s: got %v, expected %v", testcase, got, expect)) addErr(fmt.Sprintf("%s: got %v, expected %v", testcase, got, expect))
} }
} }
expectApprove := func(testcase string, err error) { expectApprove := func(testcase string, err error) {
if err == nil || err == accounts.ErrUnknownAccount { if err == nil || err == accounts.ErrUnknownAccount {
return return
} }
errs = append(errs, fmt.Sprintf("%v: expected no error, got %v", testcase, err.Error())) addErr(fmt.Sprintf("%v: expected no error, got %v", testcase, err.Error()))
} }
expectDeny := func(testcase string, err error) { expectDeny := func(testcase string, err error) {
if err == nil || err != core.ErrRequestDenied { if err == nil || err != core.ErrRequestDenied {
errs = append(errs, fmt.Sprintf("%v: expected ErrRequestDenied, got %v", testcase, err)) addErr(fmt.Sprintf("%v: expected ErrRequestDenied, got %v", testcase, err))
} }
} }
var delay = 1 * time.Second
// Test display of info and error // Test display of info and error
{ {
api.UI.ShowInfo("If you see this message, enter 'yes' to next question") api.UI.ShowInfo("If you see this message, enter 'yes' to next question")
time.Sleep(delay)
expectResponse("showinfo", "Did you see the message? [yes/no]", "yes") expectResponse("showinfo", "Did you see the message? [yes/no]", "yes")
api.UI.ShowError("If you see this message, enter 'yes' to the next question") api.UI.ShowError("If you see this message, enter 'yes' to the next question")
time.Sleep(delay)
expectResponse("showerror", "Did you see the message? [yes/no]", "yes") expectResponse("showerror", "Did you see the message? [yes/no]", "yes")
} }
{ // Sign data test - clique header { // Sign data test - clique header
api.UI.ShowInfo("Please approve the next request for signing a clique header") api.UI.ShowInfo("Please approve the next request for signing a clique header")
time.Sleep(delay)
cliqueHeader := types.Header{ cliqueHeader := types.Header{
common.HexToHash("0000H45H"), common.HexToHash("0000H45H"),
common.HexToHash("0000H45H"), common.HexToHash("0000H45H"),
...@@ -700,14 +708,27 @@ func testExternalUI(api *core.SignerAPI) { ...@@ -700,14 +708,27 @@ func testExternalUI(api *core.SignerAPI) {
_, err = api.SignData(ctx, accounts.MimetypeClique, *addr, hexutil.Encode(cliqueRlp)) _, err = api.SignData(ctx, accounts.MimetypeClique, *addr, hexutil.Encode(cliqueRlp))
expectApprove("signdata - clique header", err) expectApprove("signdata - clique header", err)
} }
{ // Sign data test - typed data
api.UI.ShowInfo("Please approve the next request for signing EIP-712 typed data")
time.Sleep(delay)
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
err := json.Unmarshal([]byte(data), &typedData)
_, err = api.SignTypedData(ctx, *addr, typedData)
expectApprove("sign 712 typed data", err)
}
{ // Sign data test - plain text { // Sign data test - plain text
api.UI.ShowInfo("Please approve the next request for signing text") api.UI.ShowInfo("Please approve the next request for signing text")
time.Sleep(delay)
addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899") addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
_, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world"))) _, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world")))
expectApprove("signdata - text", err) expectApprove("signdata - text", err)
} }
{ // Sign data test - plain text reject { // Sign data test - plain text reject
api.UI.ShowInfo("Please deny the next request for signing text") api.UI.ShowInfo("Please deny the next request for signing text")
time.Sleep(delay)
addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899") addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
_, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world"))) _, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world")))
expectDeny("signdata - text", err) expectDeny("signdata - text", err)
...@@ -715,6 +736,7 @@ func testExternalUI(api *core.SignerAPI) { ...@@ -715,6 +736,7 @@ func testExternalUI(api *core.SignerAPI) {
{ // Sign transaction { // Sign transaction
api.UI.ShowInfo("Please reject next transaction") api.UI.ShowInfo("Please reject next transaction")
time.Sleep(delay)
data := hexutil.Bytes([]byte{}) data := hexutil.Bytes([]byte{})
to := common.NewMixedcaseAddress(a) to := common.NewMixedcaseAddress(a)
tx := core.SendTxArgs{ tx := core.SendTxArgs{
...@@ -733,16 +755,19 @@ func testExternalUI(api *core.SignerAPI) { ...@@ -733,16 +755,19 @@ func testExternalUI(api *core.SignerAPI) {
} }
{ // Listing { // Listing
api.UI.ShowInfo("Please reject listing-request") api.UI.ShowInfo("Please reject listing-request")
time.Sleep(delay)
_, err := api.List(ctx) _, err := api.List(ctx)
expectDeny("list", err) expectDeny("list", err)
} }
{ // Import { // Import
api.UI.ShowInfo("Please reject new account-request") api.UI.ShowInfo("Please reject new account-request")
time.Sleep(delay)
_, err := api.New(ctx) _, err := api.New(ctx)
expectDeny("newaccount", err) expectDeny("newaccount", err)
} }
{ // Metadata { // Metadata
api.UI.ShowInfo("Please check if you see the Origin in next listing (approve or deny)") api.UI.ShowInfo("Please check if you see the Origin in next listing (approve or deny)")
time.Sleep(delay)
api.List(context.WithValue(ctx, "Origin", "origin.com")) api.List(context.WithValue(ctx, "Origin", "origin.com"))
expectResponse("metadata - origin", "Did you see origin (origin.com)? [yes/no] ", "yes") expectResponse("metadata - origin", "Did you see origin (origin.com)? [yes/no] ", "yes")
} }
......
...@@ -42,6 +42,13 @@ const ( ...@@ -42,6 +42,13 @@ const (
// HexOrDecimal256 marshals big.Int as hex or decimal. // HexOrDecimal256 marshals big.Int as hex or decimal.
type HexOrDecimal256 big.Int type HexOrDecimal256 big.Int
// NewHexOrDecimal256 creates a new HexOrDecimal256
func NewHexOrDecimal256(x int64) *HexOrDecimal256 {
b := big.NewInt(x)
h := HexOrDecimal256(*b)
return &h
}
// UnmarshalText implements encoding.TextUnmarshaler. // UnmarshalText implements encoding.TextUnmarshaler.
func (i *HexOrDecimal256) UnmarshalText(input []byte) error { func (i *HexOrDecimal256) UnmarshalText(input []byte) error {
bigint, ok := ParseBig256(string(input)) bigint, ok := ParseBig256(string(input))
......
...@@ -482,7 +482,6 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth ...@@ -482,7 +482,6 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth
return nil, err return nil, err
} }
} }
req := SignTxRequest{ req := SignTxRequest{
Transaction: args, Transaction: args,
Meta: MetadataFromContext(ctx), Meta: MetadataFromContext(ctx),
......
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"mime" "mime"
"reflect"
"regexp" "regexp"
"sort" "sort"
"strconv" "strconv"
...@@ -95,6 +96,9 @@ func (t *Type) typeName() string { ...@@ -95,6 +96,9 @@ func (t *Type) typeName() string {
} }
func (t *Type) isReferenceType() bool { func (t *Type) isReferenceType() bool {
if len(t.Type) == 0 {
return false
}
// Reference types must have a leading uppercase characer // Reference types must have a leading uppercase characer
return unicode.IsUpper([]rune(t.Type)[0]) return unicode.IsUpper([]rune(t.Type)[0])
} }
...@@ -111,7 +115,7 @@ type TypedDataMessage = map[string]interface{} ...@@ -111,7 +115,7 @@ type TypedDataMessage = map[string]interface{}
type TypedDataDomain struct { type TypedDataDomain struct {
Name string `json:"name"` Name string `json:"name"`
Version string `json:"version"` Version string `json:"version"`
ChainId *big.Int `json:"chainId"` ChainId *math.HexOrDecimal256 `json:"chainId"`
VerifyingContract string `json:"verifyingContract"` VerifyingContract string `json:"verifyingContract"`
Salt string `json:"salt"` Salt string `json:"salt"`
} }
...@@ -323,7 +327,10 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd ...@@ -323,7 +327,10 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd
} }
rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash))) rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))
sighash := crypto.Keccak256(rawData) sighash := crypto.Keccak256(rawData)
message := typedData.Format() message, err := typedData.Format()
if err != nil {
return nil, err
}
req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: rawData, Message: message, Hash: sighash} req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: rawData, Message: message, Hash: sighash}
signature, err := api.sign(addr, req, true) signature, err := api.sign(addr, req, true)
if err != nil { if err != nil {
...@@ -377,9 +384,11 @@ func (typedData *TypedData) Dependencies(primaryType string, found []string) []s ...@@ -377,9 +384,11 @@ func (typedData *TypedData) Dependencies(primaryType string, found []string) []s
func (typedData *TypedData) EncodeType(primaryType string) hexutil.Bytes { func (typedData *TypedData) EncodeType(primaryType string) hexutil.Bytes {
// Get dependencies primary first, then alphabetical // Get dependencies primary first, then alphabetical
deps := typedData.Dependencies(primaryType, []string{}) deps := typedData.Dependencies(primaryType, []string{})
if len(deps) > 0 {
slicedDeps := deps[1:] slicedDeps := deps[1:]
sort.Strings(slicedDeps) sort.Strings(slicedDeps)
deps = append([]string{primaryType}, slicedDeps...) deps = append([]string{primaryType}, slicedDeps...)
}
// Format as a string with fields // Format as a string with fields
var buffer bytes.Buffer var buffer bytes.Buffer
...@@ -476,10 +485,60 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter ...@@ -476,10 +485,60 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter
return buffer.Bytes(), nil return buffer.Bytes(), nil
} }
func parseInteger(encType string, encValue interface{}) (*big.Int, error) {
var (
length = 0
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 // EncodePrimitiveValue deals with the primitive values found
// while searching through the typed data // while searching through the typed data
func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interface{}, depth int) ([]byte, error) { func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interface{}, depth int) ([]byte, error) {
switch encType { switch encType {
case "address": case "address":
stringValue, ok := encValue.(string) stringValue, ok := encValue.(string)
...@@ -527,30 +586,11 @@ func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interf ...@@ -527,30 +586,11 @@ func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interf
} }
} }
if strings.HasPrefix(encType, "int") || strings.HasPrefix(encType, "uint") { if strings.HasPrefix(encType, "int") || strings.HasPrefix(encType, "uint") {
length := 0 b, err := parseInteger(encType, encValue)
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 { if err != nil {
return nil, fmt.Errorf("invalid size on integer: %v", lengthStr) return nil, err
}
length = atoiSize
}
bigIntValue, ok := encValue.(*big.Int)
if bigIntValue.BitLen() > length {
return nil, fmt.Errorf("integer larger than '%v'", encType)
}
if !ok {
return nil, dataMismatchError(encType, encValue)
} }
return abi.U256(bigIntValue), nil return abi.U256(b), nil
} }
return nil, fmt.Errorf("unrecognized type '%s'", encType) return nil, fmt.Errorf("unrecognized type '%s'", encType)
...@@ -649,35 +689,32 @@ func (typedData *TypedData) Map() map[string]interface{} { ...@@ -649,35 +689,32 @@ func (typedData *TypedData) Map() map[string]interface{} {
return dataMap return dataMap
} }
// PrettyPrint generates a nice output to help the users
// of clef present data in their apps
func (typedData *TypedData) PrettyPrint() string {
output := bytes.Buffer{}
formatted := typedData.Format()
for _, item := range formatted {
output.WriteString(fmt.Sprintf("%v\n", item.Pprint(0)))
}
return output.String()
}
// Format returns a representation of typedData, which can be easily displayed by a user-interface // Format returns a representation of typedData, which can be easily displayed by a user-interface
// without in-depth knowledge about 712 rules // without in-depth knowledge about 712 rules
func (typedData *TypedData) Format() []*NameValueType { 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 var nvts []*NameValueType
nvts = append(nvts, &NameValueType{ nvts = append(nvts, &NameValueType{
Name: "EIP712Domain", Name: "EIP712Domain",
Value: typedData.formatData("EIP712Domain", typedData.Domain.Map()), Value: domain,
Typ: "domain", Typ: "domain",
}) })
nvts = append(nvts, &NameValueType{ nvts = append(nvts, &NameValueType{
Name: typedData.PrimaryType, Name: typedData.PrimaryType,
Value: typedData.formatData(typedData.PrimaryType, typedData.Message), Value: ptype,
Typ: "primary type", Typ: "primary type",
}) })
return nvts return nvts, nil
} }
func (typedData *TypedData) formatData(primaryType string, data map[string]interface{}) []*NameValueType { func (typedData *TypedData) formatData(primaryType string, data map[string]interface{}) ([]*NameValueType, error) {
var output []*NameValueType var output []*NameValueType
// Add field contents. Structs and arrays have special handlers. // Add field contents. Structs and arrays have special handlers.
...@@ -694,44 +731,70 @@ func (typedData *TypedData) formatData(primaryType string, data map[string]inter ...@@ -694,44 +731,70 @@ func (typedData *TypedData) formatData(primaryType string, data map[string]inter
for _, v := range arrayValue { for _, v := range arrayValue {
if typedData.Types[parsedType] != nil { if typedData.Types[parsedType] != nil {
mapValue, _ := v.(map[string]interface{}) mapValue, _ := v.(map[string]interface{})
mapOutput := typedData.formatData(parsedType, mapValue) mapOutput, err := typedData.formatData(parsedType, mapValue)
if err != nil {
return nil, err
}
item.Value = mapOutput item.Value = mapOutput
} else { } else {
primitiveOutput := formatPrimitiveValue(field.Type, encValue) primitiveOutput, err := formatPrimitiveValue(field.Type, encValue)
if err != nil {
return nil, err
}
item.Value = primitiveOutput item.Value = primitiveOutput
} }
} }
} else if typedData.Types[field.Type] != nil { } else if typedData.Types[field.Type] != nil {
mapValue, _ := encValue.(map[string]interface{}) if mapValue, ok := encValue.(map[string]interface{}); ok {
mapOutput := typedData.formatData(field.Type, mapValue) mapOutput, err := typedData.formatData(field.Type, mapValue)
if err != nil {
return nil, err
}
item.Value = mapOutput item.Value = mapOutput
} else { } else {
primitiveOutput := formatPrimitiveValue(field.Type, encValue) item.Value = "<nil>"
}
} else {
primitiveOutput, err := formatPrimitiveValue(field.Type, encValue)
if err != nil {
return nil, err
}
item.Value = primitiveOutput item.Value = primitiveOutput
} }
output = append(output, item) output = append(output, item)
} }
return output return output, nil
} }
func formatPrimitiveValue(encType string, encValue interface{}) string { func formatPrimitiveValue(encType string, encValue interface{}) (string, error) {
switch encType { switch encType {
case "address": case "address":
stringValue, _ := encValue.(string) if stringValue, ok := encValue.(string); !ok {
return common.HexToAddress(stringValue).String() return "", fmt.Errorf("could not format value %v as address", encValue)
} else {
return common.HexToAddress(stringValue).String(), nil
}
case "bool": case "bool":
boolValue, _ := encValue.(bool) if boolValue, ok := encValue.(bool); !ok {
return fmt.Sprintf("%t", boolValue) return "", fmt.Errorf("could not format value %v as bool", encValue)
} else {
return fmt.Sprintf("%t", boolValue), nil
}
case "bytes", "string": case "bytes", "string":
return fmt.Sprintf("%s", encValue) return fmt.Sprintf("%s", encValue), nil
} }
if strings.HasPrefix(encType, "bytes") { if strings.HasPrefix(encType, "bytes") {
return fmt.Sprintf("%s", encValue) return fmt.Sprintf("%s", encValue), nil
} else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") {
bigIntValue, _ := encValue.(*big.Int)
return fmt.Sprintf("%d (0x%x)", bigIntValue, bigIntValue)
} }
return "NA" 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 // NameValueType is a very simple struct with Name, Value and Type. It's meant for simple
...@@ -762,12 +825,21 @@ func (nvt *NameValueType) Pprint(depth int) string { ...@@ -762,12 +825,21 @@ func (nvt *NameValueType) Pprint(depth int) string {
// Validate checks if the types object is conformant to the specs // Validate checks if the types object is conformant to the specs
func (t Types) validate() error { func (t Types) validate() error {
for typeKey, typeArr := range t { for typeKey, typeArr := range t {
for _, typeObj := range typeArr { if len(typeKey) == 0 {
return fmt.Errorf("empty type key")
}
for i, typeObj := range typeArr {
if len(typeObj.Type) == 0 {
return fmt.Errorf("type %v:%d: empty Type", typeKey, i)
}
if len(typeObj.Name) == 0 {
return fmt.Errorf("type %v:%d: empty Name", typeKey, i)
}
if typeKey == typeObj.Type { if typeKey == typeObj.Type {
return fmt.Errorf("type '%s' cannot reference itself", typeObj.Type) return fmt.Errorf("type '%s' cannot reference itself", typeObj.Type)
} }
if typeObj.isReferenceType() { if typeObj.isReferenceType() {
if _, exist := t[typeObj.Type]; !exist { if _, exist := t[typeObj.typeName()]; !exist {
return fmt.Errorf("reference type '%s' is undefined", typeObj.Type) return fmt.Errorf("reference type '%s' is undefined", typeObj.Type)
} }
if !typedDataReferenceTypeRegexp.MatchString(typeObj.Type) { if !typedDataReferenceTypeRegexp.MatchString(typeObj.Type) {
...@@ -895,7 +967,7 @@ func isPrimitiveTypeValid(primitiveType string) bool { ...@@ -895,7 +967,7 @@ func isPrimitiveTypeValid(primitiveType string) bool {
// validate checks if the given domain is valid, i.e. contains at least // validate checks if the given domain is valid, i.e. contains at least
// the minimum viable keys and values // the minimum viable keys and values
func (domain *TypedDataDomain) validate() error { func (domain *TypedDataDomain) validate() error {
if domain.ChainId == big.NewInt(0) { if domain.ChainId == nil {
return errors.New("chainId must be specified according to EIP-155") return errors.New("chainId must be specified according to EIP-155")
} }
......
// Copyright 2019 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
//
package core
import (
"math/big"
"testing"
)
func TestParseInteger(t *testing.T) {
for i, tt := range []struct {
t string
v interface{}
exp *big.Int
}{
{"uint32", "-123", nil},
{"int32", "-123", big.NewInt(-123)},
{"uint32", "0xff", big.NewInt(0xff)},
{"int8", "0xffff", nil},
} {
res, err := parseInteger(tt.t, tt.v)
if tt.exp == nil && res == nil {
continue
}
if tt.exp == nil && res != nil {
t.Errorf("test %d, got %v, expected nil", i, res)
continue
}
if tt.exp != nil && res == nil {
t.Errorf("test %d, got '%v', expected %v", i, err, tt.exp)
continue
}
if tt.exp.Cmp(res) != 0 {
t.Errorf("test %d, got %v expected %v", i, res, tt.exp)
}
}
}
...@@ -20,12 +20,16 @@ import ( ...@@ -20,12 +20,16 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"math/big" "io/ioutil"
"path"
"strings"
"testing" "testing"
"github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"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"
) )
...@@ -128,7 +132,7 @@ var jsonTypedData = ` ...@@ -128,7 +132,7 @@ var jsonTypedData = `
"domain": { "domain": {
"name": "Ether Mail", "name": "Ether Mail",
"version": "1", "version": "1",
"chainId": 1, "chainId": "1",
"verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
}, },
"message": { "message": {
...@@ -151,7 +155,7 @@ const primaryType = "Mail" ...@@ -151,7 +155,7 @@ const primaryType = "Mail"
var domainStandard = core.TypedDataDomain{ var domainStandard = core.TypedDataDomain{
"Ether Mail", "Ether Mail",
"1", "1",
big.NewInt(1), math.NewHexOrDecimal256(1),
"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
"", "",
} }
...@@ -241,7 +245,6 @@ func TestDomainChainId(t *testing.T) { ...@@ -241,7 +245,6 @@ func TestDomainChainId(t *testing.T) {
if _, ok := withoutChainID.Domain.Map()["chainId"]; ok { if _, ok := withoutChainID.Domain.Map()["chainId"]; ok {
t.Errorf("Expected the chainId key to not be present in the domain map") t.Errorf("Expected the chainId key to not be present in the domain map")
} }
withChainID := core.TypedData{ withChainID := core.TypedData{
Types: core.Types{ Types: core.Types{
"EIP712Domain": []core.Type{ "EIP712Domain": []core.Type{
...@@ -251,7 +254,7 @@ func TestDomainChainId(t *testing.T) { ...@@ -251,7 +254,7 @@ func TestDomainChainId(t *testing.T) {
}, },
Domain: core.TypedDataDomain{ Domain: core.TypedDataDomain{
Name: "test", Name: "test",
ChainId: big.NewInt(1), ChainId: math.NewHexOrDecimal256(1),
}, },
} }
...@@ -310,499 +313,96 @@ func TestEncodeData(t *testing.T) { ...@@ -310,499 +313,96 @@ func TestEncodeData(t *testing.T) {
} }
} }
func TestMalformedDomainkeys(t *testing.T) { func TestFormatter(t *testing.T) {
// Verifies that malformed domain keys are properly caught: var d core.TypedData
//{ err := json.Unmarshal([]byte(jsonTypedData), &d)
// "name": "Ether Mail",
// "version": "1",
// "chainId": 1,
// "vxerifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
//}
jsonTypedData := `
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Person": [
{
"name": "name",
"type": "string"
},
{
"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,
"vxerifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"from": {
"name": "Cow",
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "Bob",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"contents": "Hello, Bob!"
}
}
`
var malformedDomainTypedData core.TypedData
err := json.Unmarshal([]byte(jsonTypedData), &malformedDomainTypedData)
if err != nil {
t.Fatalf("unmarshalling failed '%v'", err)
}
_, err = malformedDomainTypedData.HashStruct("EIP712Domain", malformedDomainTypedData.Domain.Map())
if err == nil || err.Error() != "provided data '<nil>' doesn't match type 'address'" {
t.Errorf("Expected `provided data '<nil>' doesn't match type 'address'`, got '%v'", err)
}
}
func TestMalformedTypesAndExtradata(t *testing.T) {
// Verifies several quirks
// 1. Using dynamic types and only validating the prefix:
//{
// "name": "chainId",
// "type": "uint256 ... and now for something completely different"
//}
// 2. Extra data in message:
//{
// "blahonga": "zonk bonk"
//}
jsonTypedData := `
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256 ... and now for something completely different"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Person": [
{
"name": "name",
"type": "string"
},
{
"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",
"wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "Bob",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"contents": "Hello, Bob!"
}
}
`
var malformedTypedData core.TypedData
err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData)
if err != nil {
t.Fatalf("unmarshalling failed '%v'", err)
}
malformedTypedData.Types["EIP712Domain"][2].Type = "uint256"
malformedTypedData.Message["blahonga"] = "zonk bonk"
_, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message)
if err == nil || err.Error() != "there is extra data provided in the message" {
t.Errorf("Expected `there is extra data provided in the message`, got '%v'", err)
}
}
func TestTypeMismatch(t *testing.T) {
// Verifies that:
// 1. Mismatches between the given type and data, i.e. `Person` and
// the data item is a string, are properly caught:
//{
// "name": "contents",
// "type": "Person"
//},
//{
// "contents": "Hello, Bob!" <-- string not "Person"
//}
// 2. Nonexistent types are properly caught:
//{
// "name": "contents",
// "type": "Blahonga"
//}
jsonTypedData := `
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Person": [
{
"name": "name",
"type": "string"
},
{
"name": "wallet",
"type": "address"
}
],
"Mail": [
{
"name": "from",
"type": "Person"
},
{
"name": "to",
"type": "Person"
},
{
"name": "contents",
"type": "Person"
}
]
},
"primaryType": "Mail",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": 1,
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"from": {
"name": "Cow",
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "Bob",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"contents": "Hello, Bob!"
}
}
`
var mismatchTypedData core.TypedData
err := json.Unmarshal([]byte(jsonTypedData), &mismatchTypedData)
if err != nil { if err != nil {
t.Fatalf("unmarshalling failed '%v'", err) t.Fatalf("unmarshalling failed '%v'", err)
} }
_, err = mismatchTypedData.HashStruct(mismatchTypedData.PrimaryType, mismatchTypedData.Message) formatted, _ := d.Format()
if err.Error() != "provided data 'Hello, Bob!' doesn't match type 'Person'" { for _, item := range formatted {
t.Errorf("Expected `provided data 'Hello, Bob!' doesn't match type 'Person'`, got '%v'", err) fmt.Printf("'%v'\n", item.Pprint(0))
} }
mismatchTypedData.Types["Mail"][2].Type = "Blahonga" j, _ := json.Marshal(formatted)
_, err = mismatchTypedData.HashStruct(mismatchTypedData.PrimaryType, mismatchTypedData.Message) fmt.Printf("'%v'\n", string(j))
if err == nil || err.Error() != "reference type 'Blahonga' is undefined" {
t.Fatalf("Expected `reference type 'Blahonga' is undefined`, got '%v'", err)
}
} }
func TestTypeOverflow(t *testing.T) { func sign(typedData core.TypedData) ([]byte, []byte, error) {
// Verifies data that doesn't fit into it: domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map())
//{
// "test": 65536 <-- test defined as uint8
//}
var overflowTypedData core.TypedData
err := json.Unmarshal([]byte(jsonTypedData), &overflowTypedData)
if err != nil { if err != nil {
t.Fatalf("unmarshalling failed '%v'", err) return nil, nil, err
} }
// Set test to something outside uint8 typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
(overflowTypedData.Message["from"]).(map[string]interface{})["test"] = big.NewInt(65536)
_, err = overflowTypedData.HashStruct(overflowTypedData.PrimaryType, overflowTypedData.Message)
if err == nil || err.Error() != "integer larger than 'uint8'" {
t.Fatalf("Expected `integer larger than 'uint8'`, got '%v'", err)
}
(overflowTypedData.Message["from"]).(map[string]interface{})["test"] = big.NewInt(3)
(overflowTypedData.Message["to"]).(map[string]interface{})["test"] = big.NewInt(4)
_, err = overflowTypedData.HashStruct(overflowTypedData.PrimaryType, overflowTypedData.Message)
if err != nil { if err != nil {
t.Fatalf("Expected no err, got '%v'", err) return nil, nil, err
} }
rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))
sighash := crypto.Keccak256(rawData)
return typedDataHash, sighash, nil
} }
func TestArray(t *testing.T) { func TestJsonFiles(t *testing.T) {
// Makes sure that arrays work fine testfiles, err := ioutil.ReadDir("testdata/")
//{
// "type": "address[]"
//},
//{
// "type": "string[]"
//},
//{
// "type": "uint16[]",
//}
jsonTypedData := `
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Foo": [
{
"name": "bar",
"type": "address[]"
}
]
},
"primaryType": "Foo",
"domain": {
"name": "Lorem",
"version": "1",
"chainId": 1,
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"bar": [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0x0000000000000000000000000000000000000003"
]
}
}
`
var arrayTypedData core.TypedData
err := json.Unmarshal([]byte(jsonTypedData), &arrayTypedData)
if err != nil { if err != nil {
t.Fatalf("unmarshalling failed '%v'", err) t.Fatalf("failed reading files: %v", err)
} }
_, err = arrayTypedData.HashStruct(arrayTypedData.PrimaryType, arrayTypedData.Message) for i, fInfo := range testfiles {
if err != nil { if !strings.HasSuffix(fInfo.Name(), "json") {
t.Fatalf("Expected no err, got '%v'", err) continue
} }
expectedFailure := strings.HasPrefix(fInfo.Name(), "expfail")
// Change array to string data, err := ioutil.ReadFile(path.Join("testdata", fInfo.Name()))
arrayTypedData.Types["Foo"][0].Type = "string[]"
arrayTypedData.Message["bar"] = []interface{}{
"lorem",
"ipsum",
"dolores",
}
_, err = arrayTypedData.HashStruct(arrayTypedData.PrimaryType, arrayTypedData.Message)
if err != nil { if err != nil {
t.Fatalf("Expected no err, got '%v'", err) t.Errorf("Failed to read file %v: %v", fInfo.Name(), err)
continue
} }
var typedData core.TypedData
// Change array to uint err = json.Unmarshal([]byte(data), &typedData)
arrayTypedData.Types["Foo"][0].Type = "uint[]"
arrayTypedData.Message["bar"] = []interface{}{
big.NewInt(1955),
big.NewInt(108),
big.NewInt(44010),
}
_, err = arrayTypedData.HashStruct(arrayTypedData.PrimaryType, arrayTypedData.Message)
if err != nil { if err != nil {
t.Fatalf("Expected no err, got '%v'", err) t.Errorf("Test %d, file %v, json unmarshalling failed: %v", i, fInfo.Name(), err)
continue
} }
_, _, err = sign(typedData)
// Should not work with fixed-size arrays fmt.Printf("Error %v\n", err)
arrayTypedData.Types["Foo"][0].Type = "uint[3]" if err != nil && !expectedFailure {
_, err = arrayTypedData.HashStruct(arrayTypedData.PrimaryType, arrayTypedData.Message) t.Errorf("Test %d failed, file %v: %v", i, fInfo.Name(), err)
if err == nil || err.Error() != "unknown type 'uint[3]'" {
t.Fatalf("Expected `unknown type 'uint[3]'`, got '%v'", err)
} }
} if expectedFailure && err == nil {
t.Errorf("Test %d succeeded (expected failure), file %v: %v", i, fInfo.Name(), err)
func TestCustomTypeAsArray(t *testing.T) {
var jsonTypedData = `
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Person": [
{
"name": "name",
"type": "string"
},
{
"name": "wallet",
"type": "address"
}
],
"Person[]": [
{
"name": "baz",
"type": "string"
}
],
"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",
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {"baz": "foo"},
"contents": "Hello, Bob!"
} }
} }
}
` // TestFuzzerFiles tests some files that have been found by fuzzing to cause
var malformedTypedData core.TypedData // crashes or hangs.
err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) func TestFuzzerFiles(t *testing.T) {
corpusdir := path.Join("testdata", "fuzzing")
testfiles, err := ioutil.ReadDir(corpusdir)
if err != nil { if err != nil {
t.Fatalf("unmarshalling failed '%v'", err) t.Fatalf("failed reading files: %v", err)
} }
_, err = malformedTypedData.HashStruct("EIP712Domain", malformedTypedData.Domain.Map()) verbose := false
for i, fInfo := range testfiles {
data, err := ioutil.ReadFile(path.Join(corpusdir, fInfo.Name()))
if err != nil { if err != nil {
t.Errorf("Expected no error, got '%v'", err) t.Errorf("Failed to read file %v: %v", fInfo.Name(), err)
continue
} }
} var typedData core.TypedData
err = json.Unmarshal([]byte(data), &typedData)
func TestFormatter(t *testing.T) {
var d core.TypedData
err := json.Unmarshal([]byte(jsonTypedData), &d)
if err != nil { if err != nil {
t.Fatalf("unmarshalling failed '%v'", err) t.Errorf("Test %d, file %v, json unmarshalling failed: %v", i, fInfo.Name(), err)
continue
} }
formatted := d.Format() _, err = typedData.EncodeData("EIP712Domain", typedData.Domain.Map(), 1)
for _, item := range formatted { if verbose && err != nil {
fmt.Printf("'%v'\n", item.Pprint(0)) fmt.Printf("%d, EncodeData[1] err: %v\n", i, err)
}
_, err = typedData.EncodeData(typedData.PrimaryType, typedData.Message, 1)
if verbose && err != nil {
fmt.Printf("%d, EncodeData[2] err: %v\n", i, err)
}
typedData.Format()
} }
j, _ := json.Marshal(formatted)
fmt.Printf("'%v'\n", string(j))
} }
### EIP 712 tests
These tests are json files which are converted into eip-712 typed data.
All files are expected to be proper json, and tests will fail if they are not.
Files that begin with `expfail' are expected to not pass the hashstruct construction.
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Foo": [
{
"name": "addys",
"type": "address[]"
},
{
"name": "stringies",
"type": "string[]"
},
{
"name": "inties",
"type": "uint[]"
}
]
},
"primaryType": "Foo",
"domain": {
"name": "Lorem",
"version": "1",
"chainId": "1",
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"addys": [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0x0000000000000000000000000000000000000003"
],
"stringies": [
"lorem",
"ipsum",
"dolores"
],
"inties": [
"0x0000000000000000000000000000000000000001",
"3",
4.0
]
}
}
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Person": [
{
"name": "name",
"type": "string"
}
],
"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"},
"to": [{ "name": "Moose"},{ "name": "Goose"}],
"contents": "Hello, Bob!"
}
}
{
"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": "test2",
"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",
"test2": 5.0,
"wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "Bob",
"test": "0",
"test2": 5,
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"contents": "Hello, Bob!"
}
}
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Person": [
{
"name": "name",
"type": "string"
},
{
"name": "wallet",
"type": "address"
}
],
"Person[]": [
{
"name": "baz",
"type": "string"
}
],
"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",
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {"baz": "foo"},
"contents": "Hello, Bob!"
}
}
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Person": [
{
"name": "name",
"type": "string"
},
{
"name": "wallet",
"type": "address"
}
],
"Mail": [
{
"name": "from",
"type": "Person"
},
{
"name": "to",
"type": "Person"
},
{
"name": "contents",
"type": "Person"
}
]
},
"primaryType": "Mail",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": "1",
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"from": {
"name": "Cow",
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "Bob",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"contents": "Hello, Bob!"
}
}
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256 ... and now for something completely different"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Person": [
{
"name": "name",
"type": "string"
},
{
"name": "test",
"type": "uint8"
},
{
"name": "test2",
"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",
"test2": 5.0,
"wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "Bob",
"test": "0",
"test2": 5,
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"contents": "Hello, Bob!"
}
}
{
"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": "test2",
"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": {
"blahonga": "zonk bonk",
"from": {
"name": "Cow",
"test": "3",
"test2": 5.0,
"wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "Bob",
"test": "0",
"test2": 5,
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"contents": "Hello, Bob!"
}
}
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Person": [
{
"name": "name",
"type": "string"
},
{
"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",
"vFAILFAILerifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"from": {
"name": "Cow",
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "Bob",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"contents": "Hello, Bob!"
}
}
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Person": [
{
"name": "name",
"type": "string"
},
{
"name": "wallet",
"type": "address"
}
],
"Mail": [
{
"name": "from",
"type": "Person"
},
{
"name": "to",
"type": "Person"
},
{
"name": "contents",
"type": "Blahonga"
}
]
},
"primaryType": "Mail",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": "1",
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"from": {
"name": "Cow",
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "Bob",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"contents": "Hello, Bob!"
}
}
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Mail": [
{
"name": "test",
"type": "uint8"
}
]
},
"primaryType": "Mail",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": "1",
"verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"test":"257"
}
}
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Mail": [
{
"name": "test",
"type": "uint8"
}
]
},
"primaryType": "Mail",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": "1",
"verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"test":257
}
}
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Mail": [
{
"name": "test",
"type": "uint8"
}
]
},
"primaryType": "Mail",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": "1",
"verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"test":"255.3"
}
}
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Mail": [
{
"name": "test",
"type": "uint8"
}
]
},
"primaryType": "Mail",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": "1",
"verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"test": 255.3
}
}
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Mail": [
{
"name": "test",
"type": "uint8"
}
]
},
"primaryType": "Mail",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": "1",
"verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"test":"255.3"
}
}
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Foo": [
{
"name": "addys",
"type": "address[]"
},
{
"name": "stringies",
"type": "string[]"
},
{
"name": "inties",
"type": "uint[]"
}
]
},
"primaryType": "Foo",
"domain": {
"name": "Lorem",
"version": "1",
"chainId": "1",
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"addys": [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0x0000000000000000000000000000000000000003"
],
"stringies": [
"lorem",
"ipsum",
"dolores"
],
"inties": [
"0x0000000000000000000000000000000000000001",
"3",
4.0
]
}
}
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Mail": [
{
"name": "test",
"type": "uint8"
}
]
},
"primaryType": "Mail",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": "1",
"verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"test":257
}
}
{"domain":{"version":"0","chainId":""}}
\ No newline at end of file
{ "types": { "":[ {
"name": "name",
"type":"string" },
{
"name":"version",
"type": "string" }, {
"name": "chaiI",
"type":"uint256 . ad nowretig omeedifere" }, {
"ae": "eifinC",
"ty":"dess"
}
],
"Person":[
{
"name":"name",
"type": "string"
}, {
"name":"tes", "type":"it8"
},
{ "name":"t", "tye":"uit8"
},
{
"a":"ale",
"type": "ress"
}
],
"Mail": [
{
"name":"from", "type":"Person" },
{
"name": "to", "type": "Person"
},
{
"name": "contents",
"type": "string"
}
]
}, "primaryType": "Mail",
"domain": {
"name":"theMail", "version": "1",
"chainId": "1",
"verifyingntract": "0xCcccCCCcCCCCCCCcCCcCCCcCcccccC"
},
"message": { "from": {
"name": "Cow",
"test": "3",
"est2":5.0,
"llt": "0xcD2a3938E13D947E0bE734DfDD86" }, "to": { "name": "Bob",
"ts":"",
"tet2": 5,
"allet": "0bBBBBbbBBbbbbBbbBbbbbBBBbB"
},
"contents": "Hello, Bob!" }
}
\ No newline at end of file
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "int"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Person": [
{
"name": "name",
"type": "string"
},
{
"name": "wallet",
"type": "address"
}
],
"Mail": [
{
"name": "from",
"type": "Person"
},
{
"name": "to",
"type": "Mail"
},
{
"name": "s",
"type": "Person"
}
]
},
"primaryType": "Mail",
"domain": {
"name": "l",
"version": "1",
"chainId": "",
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"from": {
"name": "",
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"": ""
}
}
{"types":{"0":[{}]}}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment