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 }