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