github.com/0xsequence/ethkit@v1.25.0/ethcoder/typed_data.go (about)

     1  package ethcoder
     2  
     3  import (
     4  	"fmt"
     5  	"math/big"
     6  	"sort"
     7  
     8  	"github.com/0xsequence/ethkit/go-ethereum/common"
     9  	"github.com/0xsequence/ethkit/go-ethereum/crypto"
    10  )
    11  
    12  // EIP-712 -- https://eips.ethereum.org/EIPS/eip-712
    13  
    14  type TypedData struct {
    15  	Types       TypedDataTypes         `json:"types"`
    16  	PrimaryType string                 `json:"primaryType"`
    17  	Domain      TypedDataDomain        `json:"domain"`
    18  	Message     map[string]interface{} `json:"message"`
    19  }
    20  
    21  type TypedDataTypes map[string][]TypedDataArgument
    22  
    23  func (t TypedDataTypes) EncodeType(primaryType string) (string, error) {
    24  	args, ok := t[primaryType]
    25  	if !ok {
    26  		return "", fmt.Errorf("%s type is not defined", primaryType)
    27  	}
    28  
    29  	subTypes := []string{}
    30  	s := primaryType + "("
    31  
    32  	for i, arg := range args {
    33  		_, ok := t[arg.Type]
    34  		if ok {
    35  			set := false
    36  			for _, v := range subTypes {
    37  				if v == arg.Type {
    38  					set = true
    39  				}
    40  			}
    41  			if !set {
    42  				subTypes = append(subTypes, arg.Type)
    43  			}
    44  		}
    45  
    46  		s += arg.Type + " " + arg.Name
    47  		if i < len(args)-1 {
    48  			s += ","
    49  		}
    50  	}
    51  	s += ")"
    52  
    53  	sort.Strings(subTypes)
    54  	for _, subType := range subTypes {
    55  		subEncodeType, err := t.EncodeType(subType)
    56  		if err != nil {
    57  			return "", err
    58  		}
    59  		s += subEncodeType
    60  	}
    61  
    62  	return s, nil
    63  }
    64  
    65  func (t TypedDataTypes) TypeHash(primaryType string) ([]byte, error) {
    66  	encodeType, err := t.EncodeType(primaryType)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	return Keccak256([]byte(encodeType)), nil
    71  }
    72  
    73  type TypedDataArgument struct {
    74  	Name string `json:"name"`
    75  	Type string `json:"type"`
    76  }
    77  
    78  type TypedDataDomain struct {
    79  	Name              string          `json:"name,omitempty"`
    80  	Version           string          `json:"version,omitempty"`
    81  	ChainID           *big.Int        `json:"chainId,omitempty"`
    82  	VerifyingContract *common.Address `json:"verifyingContract,omitempty"`
    83  	Salt              *[32]byte       `json:"salt,omitempty"`
    84  }
    85  
    86  func (t TypedDataDomain) Map() map[string]interface{} {
    87  	m := map[string]interface{}{}
    88  	if t.Name != "" {
    89  		m["name"] = t.Name
    90  	}
    91  	if t.Version != "" {
    92  		m["version"] = t.Version
    93  	}
    94  	if t.ChainID != nil {
    95  		m["chainId"] = t.ChainID
    96  	}
    97  	if t.VerifyingContract != nil && t.VerifyingContract.String() != "0x0000000000000000000000000000000000000000" {
    98  		m["verifyingContract"] = *t.VerifyingContract
    99  	}
   100  	if t.Salt != nil {
   101  		m["salt"] = *t.Salt
   102  	}
   103  	return m
   104  }
   105  
   106  func (t *TypedData) HashStruct(primaryType string, data map[string]interface{}) ([]byte, error) {
   107  	typeHash, err := t.Types.TypeHash(primaryType)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	encodedData, err := t.encodeData(primaryType, data)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	v, err := SolidityPack([]string{"bytes32", "bytes"}, []interface{}{BytesToBytes32(typeHash), encodedData})
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  	return Keccak256(v), nil
   120  }
   121  
   122  func (t *TypedData) encodeData(primaryType string, data map[string]interface{}) ([]byte, error) {
   123  	args, ok := t.Types[primaryType]
   124  	if !ok {
   125  		return nil, fmt.Errorf("%s type is unknown", primaryType)
   126  	}
   127  	if len(args) != len(data) {
   128  		return nil, fmt.Errorf("encoding failed for type %s, expecting %d arguments but received %d data values", primaryType, len(args), len(data))
   129  	}
   130  
   131  	abiTypes := []string{}
   132  	abiValues := []interface{}{}
   133  
   134  	for _, arg := range args {
   135  		dataValue, ok := data[arg.Name]
   136  		if !ok {
   137  			return nil, fmt.Errorf("data value missing for type %s with argument name %s", primaryType, arg.Name)
   138  		}
   139  
   140  		switch arg.Type {
   141  		case "bytes", "string":
   142  			var bytesValue []byte
   143  			if v, ok := dataValue.([]byte); ok {
   144  				bytesValue = v
   145  			} else if v, ok := dataValue.(string); ok {
   146  				bytesValue = []byte(v)
   147  			} else {
   148  				return nil, fmt.Errorf("data value invalid for type %s with argument name %s", primaryType, arg.Name)
   149  			}
   150  			abiTypes = append(abiTypes, "bytes32")
   151  			abiValues = append(abiValues, BytesToBytes32(Keccak256(bytesValue)))
   152  
   153  		default:
   154  			dataValueString, isString := dataValue.(string)
   155  			if isString {
   156  				v, err := AbiUnmarshalStringValues([]string{arg.Type}, []string{dataValueString})
   157  				if err != nil {
   158  					return nil, fmt.Errorf("failed to unmarshal string value for type %s with argument name %s, because %w", primaryType, arg.Name, err)
   159  				}
   160  				abiValues = append(abiValues, v[0])
   161  			} else {
   162  				abiValues = append(abiValues, dataValue)
   163  			}
   164  			abiTypes = append(abiTypes, arg.Type)
   165  		}
   166  	}
   167  
   168  	if len(args) != len(abiTypes) || len(args) != len(abiValues) {
   169  		return nil, fmt.Errorf("argument encoding failed to encode all values")
   170  	}
   171  
   172  	// NOTE: each part must be bytes32
   173  	var err error
   174  	encodedTypes := make([]string, len(args))
   175  	encodedValues := make([]interface{}, len(args))
   176  	for i := 0; i < len(args); i++ {
   177  		pack, err := SolidityPack([]string{abiTypes[i]}, []interface{}{abiValues[i]})
   178  		if err != nil {
   179  			return nil, err
   180  		}
   181  		encodedValues[i], err = PadZeros(pack, 32)
   182  		if err != nil {
   183  			return nil, err
   184  		}
   185  		encodedTypes[i] = "bytes"
   186  	}
   187  
   188  	encodedData, err := SolidityPack(encodedTypes, encodedValues)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  	return encodedData, nil
   193  }
   194  
   195  func (t *TypedData) EncodeDigest() ([]byte, error) {
   196  	EIP191_HEADER := "0x1901"
   197  	eip191Header, err := HexDecode(EIP191_HEADER)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  
   202  	// Prepare hash struct for the domain
   203  	domainHash, err := t.HashStruct("EIP712Domain", t.Domain.Map())
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  
   208  	// Prepare hash struct for the message object
   209  	messageHash, err := t.HashStruct(t.PrimaryType, t.Message)
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  
   214  	hashPack, err := SolidityPack([]string{"bytes", "bytes32", "bytes32"}, []interface{}{eip191Header, domainHash, messageHash})
   215  	if err != nil {
   216  		return []byte{}, err
   217  	}
   218  	hashBytes := crypto.Keccak256(hashPack)
   219  
   220  	return hashBytes, nil
   221  }