diff --git a/cmd/clef/main.go b/cmd/clef/main.go
index 61d2811f6..3aaf898db 100644
--- a/cmd/clef/main.go
+++ b/cmd/clef/main.go
@@ -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),
diff --git a/signer/core/api.go b/signer/core/api.go
index fb68018a6..48b54b8f4 100644
--- a/signer/core/api.go
+++ b/signer/core/api.go
@@ -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"`
diff --git a/signer/core/signed_data_internal_test.go b/signer/core/apitypes/signed_data_internal_test.go
similarity index 99%
rename from signer/core/signed_data_internal_test.go
rename to signer/core/apitypes/signed_data_internal_test.go
index 9768ee0b3..121cc00de 100644
--- a/signer/core/signed_data_internal_test.go
+++ b/signer/core/apitypes/signed_data_internal_test.go
@@ -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 .
-package core
+package apitypes
import (
"bytes"
diff --git a/signer/core/apitypes/types.go b/signer/core/apitypes/types.go
index 625959219..15ab15341 100644
--- a/signer/core/apitypes/types.go
+++ b/signer/core/apitypes/types.go
@@ -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 = ""
+ }
+ } 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()
+}
diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go
index 84877ee71..663d6d131 100644
--- a/signer/core/auditlog.go
+++ b/signer/core/auditlog.go
@@ -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)
diff --git a/signer/core/gnosis_safe.go b/signer/core/gnosis_safe.go
index bdf7f837a..016b1fff3 100644
--- a/signer/core/gnosis_safe.go
+++ b/signer/core/gnosis_safe.go
@@ -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,
diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go
index daa84313d..03494c098 100644
--- a/signer/core/signed_data.go
+++ b/signer/core/signed_data.go
@@ -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 = ""
- }
- } 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
-}
diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go
index 23b7b9897..1d972d296 100644
--- a/signer/core/signed_data_test.go
+++ b/signer/core/signed_data_test.go
@@ -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)
diff --git a/signer/rules/rules_test.go b/signer/rules/rules_test.go
index d506ef2db..0ab246eea 100644
--- a/signer/rules/rules_test.go
+++ b/signer/rules/rules_test.go
@@ -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",