github.com/status-im/status-go@v1.1.0/services/typeddata/types.go (about)

     1  package typeddata
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"math/big"
     8  	"strconv"
     9  )
    10  
    11  const (
    12  	eip712Domain = "EIP712Domain"
    13  	ChainIDKey   = "chainId"
    14  )
    15  
    16  // Types define fields for each composite type.
    17  type Types map[string][]Field
    18  
    19  // Field stores name and solidity type of the field.
    20  type Field struct {
    21  	Name string `json:"name"`
    22  	Type string `json:"type"`
    23  }
    24  
    25  // Validate checks that both name and type are not empty.
    26  func (f Field) Validate() error {
    27  	if len(f.Name) == 0 {
    28  		return errors.New("`name` is required")
    29  	}
    30  	if len(f.Type) == 0 {
    31  		return errors.New("`type` is required")
    32  	}
    33  	return nil
    34  }
    35  
    36  // TypedData defines typed data according to eip-712.
    37  type TypedData struct {
    38  	Types       Types                      `json:"types"`
    39  	PrimaryType string                     `json:"primaryType"`
    40  	Domain      map[string]json.RawMessage `json:"domain"`
    41  	Message     map[string]json.RawMessage `json:"message"`
    42  }
    43  
    44  // Validate that required fields are defined.
    45  // This method doesn't check if dependencies of the main type are defined, it will be validated
    46  // when type string is computed.
    47  func (t TypedData) Validate() error {
    48  	if _, exist := t.Types[eip712Domain]; !exist {
    49  		return fmt.Errorf("`%s` must be in `types`", eip712Domain)
    50  	}
    51  	if t.PrimaryType == "" {
    52  		return errors.New("`primaryType` is required")
    53  	}
    54  	if _, exist := t.Types[t.PrimaryType]; !exist {
    55  		return fmt.Errorf("primary type `%s` not defined in types", t.PrimaryType)
    56  	}
    57  	if t.Domain == nil {
    58  		return errors.New("`domain` is required")
    59  	}
    60  	if t.Message == nil {
    61  		return errors.New("`message` is required")
    62  	}
    63  	for typ := range t.Types {
    64  		fields := t.Types[typ]
    65  		for i := range fields {
    66  			if err := fields[i].Validate(); err != nil {
    67  				return fmt.Errorf("field %d from type `%s` is invalid: %v", i, typ, err)
    68  			}
    69  		}
    70  	}
    71  	return nil
    72  }
    73  
    74  // ValidateChainID accept chain as big integer and verifies if typed data belongs to the same chain.
    75  func (t TypedData) ValidateChainID(chain *big.Int) error {
    76  	if _, exist := t.Domain[ChainIDKey]; !exist {
    77  		return fmt.Errorf("domain misses chain key %s", ChainIDKey)
    78  	}
    79  	var chainID int64
    80  	if err := json.Unmarshal(t.Domain[ChainIDKey], &chainID); err != nil {
    81  		var chainIDString string
    82  		if err = json.Unmarshal(t.Domain[ChainIDKey], &chainIDString); err != nil {
    83  			return err
    84  		}
    85  		if chainID, err = strconv.ParseInt(chainIDString, 0, 64); err != nil {
    86  			return err
    87  		}
    88  	}
    89  	if chainID != chain.Int64() {
    90  		return fmt.Errorf("chainId %d doesn't match selected chain %s", chainID, chain)
    91  	}
    92  	return nil
    93  }