github.com/aitimate-0/go-ethereum@v1.9.7/signer/core/signed_data.go (about) 1 // Copyright 2019 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package core 18 19 import ( 20 "bytes" 21 "context" 22 "errors" 23 "fmt" 24 "math/big" 25 "mime" 26 "reflect" 27 "regexp" 28 "sort" 29 "strconv" 30 "strings" 31 "unicode" 32 33 "github.com/ethereum/go-ethereum/accounts" 34 "github.com/ethereum/go-ethereum/accounts/abi" 35 "github.com/ethereum/go-ethereum/common" 36 "github.com/ethereum/go-ethereum/common/hexutil" 37 "github.com/ethereum/go-ethereum/common/math" 38 "github.com/ethereum/go-ethereum/consensus/clique" 39 "github.com/ethereum/go-ethereum/core/types" 40 "github.com/ethereum/go-ethereum/crypto" 41 "github.com/ethereum/go-ethereum/rlp" 42 ) 43 44 type SigFormat struct { 45 Mime string 46 ByteVersion byte 47 } 48 49 var ( 50 IntendedValidator = SigFormat{ 51 accounts.MimetypeDataWithValidator, 52 0x00, 53 } 54 DataTyped = SigFormat{ 55 accounts.MimetypeTypedData, 56 0x01, 57 } 58 ApplicationClique = SigFormat{ 59 accounts.MimetypeClique, 60 0x02, 61 } 62 TextPlain = SigFormat{ 63 accounts.MimetypeTextPlain, 64 0x45, 65 } 66 ) 67 68 type ValidatorData struct { 69 Address common.Address 70 Message hexutil.Bytes 71 } 72 73 type TypedData struct { 74 Types Types `json:"types"` 75 PrimaryType string `json:"primaryType"` 76 Domain TypedDataDomain `json:"domain"` 77 Message TypedDataMessage `json:"message"` 78 } 79 80 type Type struct { 81 Name string `json:"name"` 82 Type string `json:"type"` 83 } 84 85 func (t *Type) isArray() bool { 86 return strings.HasSuffix(t.Type, "[]") 87 } 88 89 // typeName returns the canonical name of the type. If the type is 'Person[]', then 90 // this method returns 'Person' 91 func (t *Type) typeName() string { 92 if strings.HasSuffix(t.Type, "[]") { 93 return strings.TrimSuffix(t.Type, "[]") 94 } 95 return t.Type 96 } 97 98 func (t *Type) isReferenceType() bool { 99 if len(t.Type) == 0 { 100 return false 101 } 102 // Reference types must have a leading uppercase characer 103 return unicode.IsUpper([]rune(t.Type)[0]) 104 } 105 106 type Types map[string][]Type 107 108 type TypePriority struct { 109 Type string 110 Value uint 111 } 112 113 type TypedDataMessage = map[string]interface{} 114 115 type TypedDataDomain struct { 116 Name string `json:"name"` 117 Version string `json:"version"` 118 ChainId *math.HexOrDecimal256 `json:"chainId"` 119 VerifyingContract string `json:"verifyingContract"` 120 Salt string `json:"salt"` 121 } 122 123 var typedDataReferenceTypeRegexp = regexp.MustCompile(`^[A-Z](\w*)(\[\])?$`) 124 125 // sign receives a request and produces a signature 126 // 127 // Note, the produced signature conforms to the secp256k1 curve R, S and V values, 128 // where the V value will be 27 or 28 for legacy reasons, if legacyV==true. 129 func (api *SignerAPI) sign(addr common.MixedcaseAddress, req *SignDataRequest, legacyV bool) (hexutil.Bytes, error) { 130 // We make the request prior to looking up if we actually have the account, to prevent 131 // account-enumeration via the API 132 res, err := api.UI.ApproveSignData(req) 133 if err != nil { 134 return nil, err 135 } 136 if !res.Approved { 137 return nil, ErrRequestDenied 138 } 139 // Look up the wallet containing the requested signer 140 account := accounts.Account{Address: addr.Address()} 141 wallet, err := api.am.Find(account) 142 if err != nil { 143 return nil, err 144 } 145 pw, err := api.lookupOrQueryPassword(account.Address, 146 "Password for signing", 147 fmt.Sprintf("Please enter password for signing data with account %s", account.Address.Hex())) 148 if err != nil { 149 return nil, err 150 } 151 // Sign the data with the wallet 152 signature, err := wallet.SignDataWithPassphrase(account, pw, req.ContentType, req.Rawdata) 153 if err != nil { 154 return nil, err 155 } 156 if legacyV { 157 signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper 158 } 159 return signature, nil 160 } 161 162 // SignData signs the hash of the provided data, but does so differently 163 // depending on the content-type specified. 164 // 165 // Different types of validation occur. 166 func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error) { 167 var req, transformV, err = api.determineSignatureFormat(ctx, contentType, addr, data) 168 if err != nil { 169 return nil, err 170 } 171 signature, err := api.sign(addr, req, transformV) 172 if err != nil { 173 api.UI.ShowError(err.Error()) 174 return nil, err 175 } 176 return signature, nil 177 } 178 179 // determineSignatureFormat determines which signature method should be used based upon the mime type 180 // In the cases where it matters ensure that the charset is handled. The charset 181 // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType 182 // charset, ok := params["charset"] 183 // As it is now, we accept any charset and just treat it as 'raw'. 184 // This method returns the mimetype for signing along with the request 185 func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (*SignDataRequest, bool, error) { 186 var ( 187 req *SignDataRequest 188 useEthereumV = true // Default to use V = 27 or 28, the legacy Ethereum format 189 ) 190 mediaType, _, err := mime.ParseMediaType(contentType) 191 if err != nil { 192 return nil, useEthereumV, err 193 } 194 195 switch mediaType { 196 case IntendedValidator.Mime: 197 // Data with an intended validator 198 validatorData, err := UnmarshalValidatorData(data) 199 if err != nil { 200 return nil, useEthereumV, err 201 } 202 sighash, msg := SignTextValidator(validatorData) 203 messages := []*NameValueType{ 204 { 205 Name: "This is a request to sign data intended for a particular validator (see EIP 191 version 0)", 206 Typ: "description", 207 Value: "", 208 }, 209 { 210 Name: "Intended validator address", 211 Typ: "address", 212 Value: validatorData.Address.String(), 213 }, 214 { 215 Name: "Application-specific data", 216 Typ: "hexdata", 217 Value: validatorData.Message, 218 }, 219 { 220 Name: "Full message for signing", 221 Typ: "hexdata", 222 Value: fmt.Sprintf("0x%x", msg), 223 }, 224 } 225 req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Messages: messages, Hash: sighash} 226 case ApplicationClique.Mime: 227 // Clique is the Ethereum PoA standard 228 stringData, ok := data.(string) 229 if !ok { 230 return nil, useEthereumV, fmt.Errorf("input for %v must be an hex-encoded string", ApplicationClique.Mime) 231 } 232 cliqueData, err := hexutil.Decode(stringData) 233 if err != nil { 234 return nil, useEthereumV, err 235 } 236 header := &types.Header{} 237 if err := rlp.DecodeBytes(cliqueData, header); err != nil { 238 return nil, useEthereumV, err 239 } 240 // The incoming clique header is already truncated, sent to us with a extradata already shortened 241 if len(header.Extra) < 65 { 242 // Need to add it back, to get a suitable length for hashing 243 newExtra := make([]byte, len(header.Extra)+65) 244 copy(newExtra, header.Extra) 245 header.Extra = newExtra 246 } 247 // Get back the rlp data, encoded by us 248 sighash, cliqueRlp, err := cliqueHeaderHashAndRlp(header) 249 if err != nil { 250 return nil, useEthereumV, err 251 } 252 messages := []*NameValueType{ 253 { 254 Name: "Clique header", 255 Typ: "clique", 256 Value: fmt.Sprintf("clique header %d [0x%x]", header.Number, header.Hash()), 257 }, 258 } 259 // Clique uses V on the form 0 or 1 260 useEthereumV = false 261 req = &SignDataRequest{ContentType: mediaType, Rawdata: cliqueRlp, Messages: messages, Hash: sighash} 262 default: // also case TextPlain.Mime: 263 // Calculates an Ethereum ECDSA signature for: 264 // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") 265 // We expect it to be a string 266 if stringData, ok := data.(string); !ok { 267 return nil, useEthereumV, fmt.Errorf("input for text/plain must be an hex-encoded string") 268 } else { 269 if textData, err := hexutil.Decode(stringData); err != nil { 270 return nil, useEthereumV, err 271 } else { 272 sighash, msg := accounts.TextAndHash(textData) 273 messages := []*NameValueType{ 274 { 275 Name: "message", 276 Typ: accounts.MimetypeTextPlain, 277 Value: msg, 278 }, 279 } 280 req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Messages: messages, Hash: sighash} 281 } 282 } 283 } 284 req.Address = addr 285 req.Meta = MetadataFromContext(ctx) 286 return req, useEthereumV, nil 287 } 288 289 // SignTextWithValidator signs the given message which can be further recovered 290 // with the given validator. 291 // hash = keccak256("\x19\x00"${address}${data}). 292 func SignTextValidator(validatorData ValidatorData) (hexutil.Bytes, string) { 293 msg := fmt.Sprintf("\x19\x00%s%s", string(validatorData.Address.Bytes()), string(validatorData.Message)) 294 return crypto.Keccak256([]byte(msg)), msg 295 } 296 297 // cliqueHeaderHashAndRlp returns the hash which is used as input for the proof-of-authority 298 // signing. It is the hash of the entire header apart from the 65 byte signature 299 // contained at the end of the extra data. 300 // 301 // The method requires the extra data to be at least 65 bytes -- the original implementation 302 // in clique.go panics if this is the case, thus it's been reimplemented here to avoid the panic 303 // and simply return an error instead 304 func cliqueHeaderHashAndRlp(header *types.Header) (hash, rlp []byte, err error) { 305 if len(header.Extra) < 65 { 306 err = fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) 307 return 308 } 309 rlp = clique.CliqueRLP(header) 310 hash = clique.SealHash(header).Bytes() 311 return hash, rlp, err 312 } 313 314 // SignTypedData signs EIP-712 conformant typed data 315 // hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}") 316 func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, typedData TypedData) (hexutil.Bytes, error) { 317 domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) 318 if err != nil { 319 return nil, err 320 } 321 typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) 322 if err != nil { 323 return nil, err 324 } 325 rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash))) 326 sighash := crypto.Keccak256(rawData) 327 messages, err := typedData.Format() 328 if err != nil { 329 return nil, err 330 } 331 req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: rawData, Messages: messages, Hash: sighash} 332 signature, err := api.sign(addr, req, true) 333 if err != nil { 334 api.UI.ShowError(err.Error()) 335 return nil, err 336 } 337 return signature, nil 338 } 339 340 // HashStruct generates a keccak256 hash of the encoding of the provided data 341 func (typedData *TypedData) HashStruct(primaryType string, data TypedDataMessage) (hexutil.Bytes, error) { 342 encodedData, err := typedData.EncodeData(primaryType, data, 1) 343 if err != nil { 344 return nil, err 345 } 346 return crypto.Keccak256(encodedData), nil 347 } 348 349 // Dependencies returns an array of custom types ordered by their hierarchical reference tree 350 func (typedData *TypedData) Dependencies(primaryType string, found []string) []string { 351 includes := func(arr []string, str string) bool { 352 for _, obj := range arr { 353 if obj == str { 354 return true 355 } 356 } 357 return false 358 } 359 360 if includes(found, primaryType) { 361 return found 362 } 363 if typedData.Types[primaryType] == nil { 364 return found 365 } 366 found = append(found, primaryType) 367 for _, field := range typedData.Types[primaryType] { 368 for _, dep := range typedData.Dependencies(field.Type, found) { 369 if !includes(found, dep) { 370 found = append(found, dep) 371 } 372 } 373 } 374 return found 375 } 376 377 // EncodeType generates the following encoding: 378 // `name ‖ "(" ‖ member₁ ‖ "," ‖ member₂ ‖ "," ‖ … ‖ memberₙ ")"` 379 // 380 // each member is written as `type ‖ " " ‖ name` encodings cascade down and are sorted by name 381 func (typedData *TypedData) EncodeType(primaryType string) hexutil.Bytes { 382 // Get dependencies primary first, then alphabetical 383 deps := typedData.Dependencies(primaryType, []string{}) 384 if len(deps) > 0 { 385 slicedDeps := deps[1:] 386 sort.Strings(slicedDeps) 387 deps = append([]string{primaryType}, slicedDeps...) 388 } 389 390 // Format as a string with fields 391 var buffer bytes.Buffer 392 for _, dep := range deps { 393 buffer.WriteString(dep) 394 buffer.WriteString("(") 395 for _, obj := range typedData.Types[dep] { 396 buffer.WriteString(obj.Type) 397 buffer.WriteString(" ") 398 buffer.WriteString(obj.Name) 399 buffer.WriteString(",") 400 } 401 buffer.Truncate(buffer.Len() - 1) 402 buffer.WriteString(")") 403 } 404 return buffer.Bytes() 405 } 406 407 // TypeHash creates the keccak256 hash of the data 408 func (typedData *TypedData) TypeHash(primaryType string) hexutil.Bytes { 409 return crypto.Keccak256(typedData.EncodeType(primaryType)) 410 } 411 412 // EncodeData generates the following encoding: 413 // `enc(value₁) ‖ enc(value₂) ‖ … ‖ enc(valueₙ)` 414 // 415 // each encoded member is 32-byte long 416 func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}, depth int) (hexutil.Bytes, error) { 417 if err := typedData.validate(); err != nil { 418 return nil, err 419 } 420 421 buffer := bytes.Buffer{} 422 423 // Verify extra data 424 if len(typedData.Types[primaryType]) < len(data) { 425 return nil, errors.New("there is extra data provided in the message") 426 } 427 428 // Add typehash 429 buffer.Write(typedData.TypeHash(primaryType)) 430 431 // Add field contents. Structs and arrays have special handlers. 432 for _, field := range typedData.Types[primaryType] { 433 encType := field.Type 434 encValue := data[field.Name] 435 if encType[len(encType)-1:] == "]" { 436 arrayValue, ok := encValue.([]interface{}) 437 if !ok { 438 return nil, dataMismatchError(encType, encValue) 439 } 440 441 arrayBuffer := bytes.Buffer{} 442 parsedType := strings.Split(encType, "[")[0] 443 for _, item := range arrayValue { 444 if typedData.Types[parsedType] != nil { 445 mapValue, ok := item.(map[string]interface{}) 446 if !ok { 447 return nil, dataMismatchError(parsedType, item) 448 } 449 encodedData, err := typedData.EncodeData(parsedType, mapValue, depth+1) 450 if err != nil { 451 return nil, err 452 } 453 arrayBuffer.Write(encodedData) 454 } else { 455 bytesValue, err := typedData.EncodePrimitiveValue(parsedType, item, depth) 456 if err != nil { 457 return nil, err 458 } 459 arrayBuffer.Write(bytesValue) 460 } 461 } 462 463 buffer.Write(crypto.Keccak256(arrayBuffer.Bytes())) 464 } else if typedData.Types[field.Type] != nil { 465 mapValue, ok := encValue.(map[string]interface{}) 466 if !ok { 467 return nil, dataMismatchError(encType, encValue) 468 } 469 encodedData, err := typedData.EncodeData(field.Type, mapValue, depth+1) 470 if err != nil { 471 return nil, err 472 } 473 buffer.Write(crypto.Keccak256(encodedData)) 474 } else { 475 byteValue, err := typedData.EncodePrimitiveValue(encType, encValue, depth) 476 if err != nil { 477 return nil, err 478 } 479 buffer.Write(byteValue) 480 } 481 } 482 return buffer.Bytes(), nil 483 } 484 485 func parseInteger(encType string, encValue interface{}) (*big.Int, error) { 486 var ( 487 length int 488 signed = strings.HasPrefix(encType, "int") 489 b *big.Int 490 ) 491 if encType == "int" || encType == "uint" { 492 length = 256 493 } else { 494 lengthStr := "" 495 if strings.HasPrefix(encType, "uint") { 496 lengthStr = strings.TrimPrefix(encType, "uint") 497 } else { 498 lengthStr = strings.TrimPrefix(encType, "int") 499 } 500 atoiSize, err := strconv.Atoi(lengthStr) 501 if err != nil { 502 return nil, fmt.Errorf("invalid size on integer: %v", lengthStr) 503 } 504 length = atoiSize 505 } 506 switch v := encValue.(type) { 507 case *math.HexOrDecimal256: 508 b = (*big.Int)(v) 509 case string: 510 var hexIntValue math.HexOrDecimal256 511 if err := hexIntValue.UnmarshalText([]byte(v)); err != nil { 512 return nil, err 513 } 514 b = (*big.Int)(&hexIntValue) 515 case float64: 516 // JSON parses non-strings as float64. Fail if we cannot 517 // convert it losslessly 518 if float64(int64(v)) == v { 519 b = big.NewInt(int64(v)) 520 } else { 521 return nil, fmt.Errorf("invalid float value %v for type %v", v, encType) 522 } 523 } 524 if b == nil { 525 return nil, fmt.Errorf("invalid integer value %v/%v for type %v", encValue, reflect.TypeOf(encValue), encType) 526 } 527 if b.BitLen() > length { 528 return nil, fmt.Errorf("integer larger than '%v'", encType) 529 } 530 if !signed && b.Sign() == -1 { 531 return nil, fmt.Errorf("invalid negative value for unsigned type %v", encType) 532 } 533 return b, nil 534 } 535 536 // EncodePrimitiveValue deals with the primitive values found 537 // while searching through the typed data 538 func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interface{}, depth int) ([]byte, error) { 539 switch encType { 540 case "address": 541 stringValue, ok := encValue.(string) 542 if !ok || !common.IsHexAddress(stringValue) { 543 return nil, dataMismatchError(encType, encValue) 544 } 545 retval := make([]byte, 32) 546 copy(retval[12:], common.HexToAddress(stringValue).Bytes()) 547 return retval, nil 548 case "bool": 549 boolValue, ok := encValue.(bool) 550 if !ok { 551 return nil, dataMismatchError(encType, encValue) 552 } 553 if boolValue { 554 return math.PaddedBigBytes(common.Big1, 32), nil 555 } 556 return math.PaddedBigBytes(common.Big0, 32), nil 557 case "string": 558 strVal, ok := encValue.(string) 559 if !ok { 560 return nil, dataMismatchError(encType, encValue) 561 } 562 return crypto.Keccak256([]byte(strVal)), nil 563 case "bytes": 564 bytesValue, ok := encValue.([]byte) 565 if !ok { 566 return nil, dataMismatchError(encType, encValue) 567 } 568 return crypto.Keccak256(bytesValue), nil 569 } 570 if strings.HasPrefix(encType, "bytes") { 571 lengthStr := strings.TrimPrefix(encType, "bytes") 572 length, err := strconv.Atoi(lengthStr) 573 if err != nil { 574 return nil, fmt.Errorf("invalid size on bytes: %v", lengthStr) 575 } 576 if length < 0 || length > 32 { 577 return nil, fmt.Errorf("invalid size on bytes: %d", length) 578 } 579 if byteValue, ok := encValue.(hexutil.Bytes); !ok { 580 return nil, dataMismatchError(encType, encValue) 581 } else { 582 return math.PaddedBigBytes(new(big.Int).SetBytes(byteValue), 32), nil 583 } 584 } 585 if strings.HasPrefix(encType, "int") || strings.HasPrefix(encType, "uint") { 586 b, err := parseInteger(encType, encValue) 587 if err != nil { 588 return nil, err 589 } 590 return abi.U256(b), nil 591 } 592 return nil, fmt.Errorf("unrecognized type '%s'", encType) 593 594 } 595 596 // dataMismatchError generates an error for a mismatch between 597 // the provided type and data 598 func dataMismatchError(encType string, encValue interface{}) error { 599 return fmt.Errorf("provided data '%v' doesn't match type '%s'", encValue, encType) 600 } 601 602 // EcRecover recovers the address associated with the given sig. 603 // Only compatible with `text/plain` 604 func (api *SignerAPI) EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { 605 // Returns the address for the Account that was used to create the signature. 606 // 607 // Note, this function is compatible with eth_sign and personal_sign. As such it recovers 608 // the address of: 609 // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") 610 // addr = ecrecover(hash, signature) 611 // 612 // Note, the signature must conform to the secp256k1 curve R, S and V values, where 613 // the V value must be be 27 or 28 for legacy reasons. 614 // 615 // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover 616 if len(sig) != 65 { 617 return common.Address{}, fmt.Errorf("signature must be 65 bytes long") 618 } 619 if sig[64] != 27 && sig[64] != 28 { 620 return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") 621 } 622 sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 623 hash := accounts.TextHash(data) 624 rpk, err := crypto.SigToPub(hash, sig) 625 if err != nil { 626 return common.Address{}, err 627 } 628 return crypto.PubkeyToAddress(*rpk), nil 629 } 630 631 // UnmarshalValidatorData converts the bytes input to typed data 632 func UnmarshalValidatorData(data interface{}) (ValidatorData, error) { 633 raw, ok := data.(map[string]interface{}) 634 if !ok { 635 return ValidatorData{}, errors.New("validator input is not a map[string]interface{}") 636 } 637 addr, ok := raw["address"].(string) 638 if !ok { 639 return ValidatorData{}, errors.New("validator address is not sent as a string") 640 } 641 addrBytes, err := hexutil.Decode(addr) 642 if err != nil { 643 return ValidatorData{}, err 644 } 645 if !ok || len(addrBytes) == 0 { 646 return ValidatorData{}, errors.New("validator address is undefined") 647 } 648 649 message, ok := raw["message"].(string) 650 if !ok { 651 return ValidatorData{}, errors.New("message is not sent as a string") 652 } 653 messageBytes, err := hexutil.Decode(message) 654 if err != nil { 655 return ValidatorData{}, err 656 } 657 if !ok || len(messageBytes) == 0 { 658 return ValidatorData{}, errors.New("message is undefined") 659 } 660 661 return ValidatorData{ 662 Address: common.BytesToAddress(addrBytes), 663 Message: messageBytes, 664 }, nil 665 } 666 667 // validate makes sure the types are sound 668 func (typedData *TypedData) validate() error { 669 if err := typedData.Types.validate(); err != nil { 670 return err 671 } 672 if err := typedData.Domain.validate(); err != nil { 673 return err 674 } 675 return nil 676 } 677 678 // Map generates a map version of the typed data 679 func (typedData *TypedData) Map() map[string]interface{} { 680 dataMap := map[string]interface{}{ 681 "types": typedData.Types, 682 "domain": typedData.Domain.Map(), 683 "primaryType": typedData.PrimaryType, 684 "message": typedData.Message, 685 } 686 return dataMap 687 } 688 689 // Format returns a representation of typedData, which can be easily displayed by a user-interface 690 // without in-depth knowledge about 712 rules 691 func (typedData *TypedData) Format() ([]*NameValueType, error) { 692 domain, err := typedData.formatData("EIP712Domain", typedData.Domain.Map()) 693 if err != nil { 694 return nil, err 695 } 696 ptype, err := typedData.formatData(typedData.PrimaryType, typedData.Message) 697 if err != nil { 698 return nil, err 699 } 700 var nvts []*NameValueType 701 nvts = append(nvts, &NameValueType{ 702 Name: "EIP712Domain", 703 Value: domain, 704 Typ: "domain", 705 }) 706 nvts = append(nvts, &NameValueType{ 707 Name: typedData.PrimaryType, 708 Value: ptype, 709 Typ: "primary type", 710 }) 711 return nvts, nil 712 } 713 714 func (typedData *TypedData) formatData(primaryType string, data map[string]interface{}) ([]*NameValueType, error) { 715 var output []*NameValueType 716 717 // Add field contents. Structs and arrays have special handlers. 718 for _, field := range typedData.Types[primaryType] { 719 encName := field.Name 720 encValue := data[encName] 721 item := &NameValueType{ 722 Name: encName, 723 Typ: field.Type, 724 } 725 if field.isArray() { 726 arrayValue, _ := encValue.([]interface{}) 727 parsedType := field.typeName() 728 for _, v := range arrayValue { 729 if typedData.Types[parsedType] != nil { 730 mapValue, _ := v.(map[string]interface{}) 731 mapOutput, err := typedData.formatData(parsedType, mapValue) 732 if err != nil { 733 return nil, err 734 } 735 item.Value = mapOutput 736 } else { 737 primitiveOutput, err := formatPrimitiveValue(field.Type, encValue) 738 if err != nil { 739 return nil, err 740 } 741 item.Value = primitiveOutput 742 } 743 } 744 } else if typedData.Types[field.Type] != nil { 745 if mapValue, ok := encValue.(map[string]interface{}); ok { 746 mapOutput, err := typedData.formatData(field.Type, mapValue) 747 if err != nil { 748 return nil, err 749 } 750 item.Value = mapOutput 751 } else { 752 item.Value = "<nil>" 753 } 754 } else { 755 primitiveOutput, err := formatPrimitiveValue(field.Type, encValue) 756 if err != nil { 757 return nil, err 758 } 759 item.Value = primitiveOutput 760 } 761 output = append(output, item) 762 } 763 return output, nil 764 } 765 766 func formatPrimitiveValue(encType string, encValue interface{}) (string, error) { 767 switch encType { 768 case "address": 769 if stringValue, ok := encValue.(string); !ok { 770 return "", fmt.Errorf("could not format value %v as address", encValue) 771 } else { 772 return common.HexToAddress(stringValue).String(), nil 773 } 774 case "bool": 775 if boolValue, ok := encValue.(bool); !ok { 776 return "", fmt.Errorf("could not format value %v as bool", encValue) 777 } else { 778 return fmt.Sprintf("%t", boolValue), nil 779 } 780 case "bytes", "string": 781 return fmt.Sprintf("%s", encValue), nil 782 } 783 if strings.HasPrefix(encType, "bytes") { 784 return fmt.Sprintf("%s", encValue), nil 785 786 } 787 if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { 788 if b, err := parseInteger(encType, encValue); err != nil { 789 return "", err 790 } else { 791 return fmt.Sprintf("%d (0x%x)", b, b), nil 792 } 793 } 794 return "", fmt.Errorf("unhandled type %v", encType) 795 } 796 797 // NameValueType is a very simple struct with Name, Value and Type. It's meant for simple 798 // json structures used to communicate signing-info about typed data with the UI 799 type NameValueType struct { 800 Name string `json:"name"` 801 Value interface{} `json:"value"` 802 Typ string `json:"type"` 803 } 804 805 // Pprint returns a pretty-printed version of nvt 806 func (nvt *NameValueType) Pprint(depth int) string { 807 output := bytes.Buffer{} 808 output.WriteString(strings.Repeat("\u00a0", depth*2)) 809 output.WriteString(fmt.Sprintf("%s [%s]: ", nvt.Name, nvt.Typ)) 810 if nvts, ok := nvt.Value.([]*NameValueType); ok { 811 output.WriteString("\n") 812 for _, next := range nvts { 813 sublevel := next.Pprint(depth + 1) 814 output.WriteString(sublevel) 815 } 816 } else { 817 output.WriteString(fmt.Sprintf("%q\n", nvt.Value)) 818 } 819 return output.String() 820 } 821 822 // Validate checks if the types object is conformant to the specs 823 func (t Types) validate() error { 824 for typeKey, typeArr := range t { 825 if len(typeKey) == 0 { 826 return fmt.Errorf("empty type key") 827 } 828 for i, typeObj := range typeArr { 829 if len(typeObj.Type) == 0 { 830 return fmt.Errorf("type %v:%d: empty Type", typeKey, i) 831 } 832 if len(typeObj.Name) == 0 { 833 return fmt.Errorf("type %v:%d: empty Name", typeKey, i) 834 } 835 if typeKey == typeObj.Type { 836 return fmt.Errorf("type '%s' cannot reference itself", typeObj.Type) 837 } 838 if typeObj.isReferenceType() { 839 if _, exist := t[typeObj.typeName()]; !exist { 840 return fmt.Errorf("reference type '%s' is undefined", typeObj.Type) 841 } 842 if !typedDataReferenceTypeRegexp.MatchString(typeObj.Type) { 843 return fmt.Errorf("unknown reference type '%s", typeObj.Type) 844 } 845 } else if !isPrimitiveTypeValid(typeObj.Type) { 846 return fmt.Errorf("unknown type '%s'", typeObj.Type) 847 } 848 } 849 } 850 return nil 851 } 852 853 // Checks if the primitive value is valid 854 func isPrimitiveTypeValid(primitiveType string) bool { 855 if primitiveType == "address" || 856 primitiveType == "address[]" || 857 primitiveType == "bool" || 858 primitiveType == "bool[]" || 859 primitiveType == "string" || 860 primitiveType == "string[]" { 861 return true 862 } 863 if primitiveType == "bytes" || 864 primitiveType == "bytes[]" || 865 primitiveType == "bytes1" || 866 primitiveType == "bytes1[]" || 867 primitiveType == "bytes2" || 868 primitiveType == "bytes2[]" || 869 primitiveType == "bytes3" || 870 primitiveType == "bytes3[]" || 871 primitiveType == "bytes4" || 872 primitiveType == "bytes4[]" || 873 primitiveType == "bytes5" || 874 primitiveType == "bytes5[]" || 875 primitiveType == "bytes6" || 876 primitiveType == "bytes6[]" || 877 primitiveType == "bytes7" || 878 primitiveType == "bytes7[]" || 879 primitiveType == "bytes8" || 880 primitiveType == "bytes8[]" || 881 primitiveType == "bytes9" || 882 primitiveType == "bytes9[]" || 883 primitiveType == "bytes10" || 884 primitiveType == "bytes10[]" || 885 primitiveType == "bytes11" || 886 primitiveType == "bytes11[]" || 887 primitiveType == "bytes12" || 888 primitiveType == "bytes12[]" || 889 primitiveType == "bytes13" || 890 primitiveType == "bytes13[]" || 891 primitiveType == "bytes14" || 892 primitiveType == "bytes14[]" || 893 primitiveType == "bytes15" || 894 primitiveType == "bytes15[]" || 895 primitiveType == "bytes16" || 896 primitiveType == "bytes16[]" || 897 primitiveType == "bytes17" || 898 primitiveType == "bytes17[]" || 899 primitiveType == "bytes18" || 900 primitiveType == "bytes18[]" || 901 primitiveType == "bytes19" || 902 primitiveType == "bytes19[]" || 903 primitiveType == "bytes20" || 904 primitiveType == "bytes20[]" || 905 primitiveType == "bytes21" || 906 primitiveType == "bytes21[]" || 907 primitiveType == "bytes22" || 908 primitiveType == "bytes22[]" || 909 primitiveType == "bytes23" || 910 primitiveType == "bytes23[]" || 911 primitiveType == "bytes24" || 912 primitiveType == "bytes24[]" || 913 primitiveType == "bytes25" || 914 primitiveType == "bytes25[]" || 915 primitiveType == "bytes26" || 916 primitiveType == "bytes26[]" || 917 primitiveType == "bytes27" || 918 primitiveType == "bytes27[]" || 919 primitiveType == "bytes28" || 920 primitiveType == "bytes28[]" || 921 primitiveType == "bytes29" || 922 primitiveType == "bytes29[]" || 923 primitiveType == "bytes30" || 924 primitiveType == "bytes30[]" || 925 primitiveType == "bytes31" || 926 primitiveType == "bytes31[]" { 927 return true 928 } 929 if primitiveType == "int" || 930 primitiveType == "int[]" || 931 primitiveType == "int8" || 932 primitiveType == "int8[]" || 933 primitiveType == "int16" || 934 primitiveType == "int16[]" || 935 primitiveType == "int32" || 936 primitiveType == "int32[]" || 937 primitiveType == "int64" || 938 primitiveType == "int64[]" || 939 primitiveType == "int128" || 940 primitiveType == "int128[]" || 941 primitiveType == "int256" || 942 primitiveType == "int256[]" { 943 return true 944 } 945 if primitiveType == "uint" || 946 primitiveType == "uint[]" || 947 primitiveType == "uint8" || 948 primitiveType == "uint8[]" || 949 primitiveType == "uint16" || 950 primitiveType == "uint16[]" || 951 primitiveType == "uint32" || 952 primitiveType == "uint32[]" || 953 primitiveType == "uint64" || 954 primitiveType == "uint64[]" || 955 primitiveType == "uint128" || 956 primitiveType == "uint128[]" || 957 primitiveType == "uint256" || 958 primitiveType == "uint256[]" { 959 return true 960 } 961 return false 962 } 963 964 // validate checks if the given domain is valid, i.e. contains at least 965 // the minimum viable keys and values 966 func (domain *TypedDataDomain) validate() error { 967 if domain.ChainId == nil { 968 return errors.New("chainId must be specified according to EIP-155") 969 } 970 971 if len(domain.Name) == 0 && len(domain.Version) == 0 && len(domain.VerifyingContract) == 0 && len(domain.Salt) == 0 { 972 return errors.New("domain is undefined") 973 } 974 975 return nil 976 } 977 978 // Map is a helper function to generate a map version of the domain 979 func (domain *TypedDataDomain) Map() map[string]interface{} { 980 dataMap := map[string]interface{}{} 981 982 if domain.ChainId != nil { 983 dataMap["chainId"] = domain.ChainId 984 } 985 986 if len(domain.Name) > 0 { 987 dataMap["name"] = domain.Name 988 } 989 990 if len(domain.Version) > 0 { 991 dataMap["version"] = domain.Version 992 } 993 994 if len(domain.VerifyingContract) > 0 { 995 dataMap["verifyingContract"] = domain.VerifyingContract 996 } 997 998 if len(domain.Salt) > 0 { 999 dataMap["salt"] = domain.Salt 1000 } 1001 return dataMap 1002 }