From a7003c7032b2271cb4094d8c6e67f42e4a6a3bbd Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Thu, 18 Oct 2018 09:16:43 +0100 Subject: [PATCH] Polished docstrings, ran goimports and swapped fmt.Errorf with errors.New where possible --- signer/core/api_test.go | 32 ++--- signer/core/{apiv2.go => signed_data.go} | 175 +++++++++++++++-------- 2 files changed, 130 insertions(+), 77 deletions(-) rename signer/core/{apiv2.go => signed_data.go} (54%) diff --git a/signer/core/api_test.go b/signer/core/api_test.go index bf49dfaa94..f1695feec9 100644 --- a/signer/core/api_test.go +++ b/signer/core/api_test.go @@ -245,21 +245,15 @@ func TestNewAcc(t *testing.T) { } } -func signApplicationValidator(t *testing.T) { +func signTextValidator(t *testing.T) { // TODO } func signApplicationClique(t *testing.T) { - // https://etherscan.io/block/1 - //header := &types.Header{ - // "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", - // "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - // "0x05a56e2d52c817161883f50c441c3228cfe54d9f", - //} // TODO } -func signDataPlain(t *testing.T) { +func signTextPlain(t *testing.T) { api, control := setup(t) //Create two accounts createAccount(control, api, t) @@ -273,7 +267,7 @@ func signDataPlain(t *testing.T) { control <- "Y" control <- "wrongpassword" - h, err := api.SignData(context.Background(), DataPlain.Mime, a, []byte("EHLO world")) + h, err := api.SignData(context.Background(), TextPlain.Mime, a, []byte("EHLO world")) if h != nil { t.Errorf("Expected nil-data, got %x", h) } @@ -281,7 +275,7 @@ func signDataPlain(t *testing.T) { t.Errorf("Expected ErrLocked! %v", err) } control <- "No way" - h, err = api.SignData(context.Background(), DataPlain.Mime, a, []byte("EHLO world")) + h, err = api.SignData(context.Background(), TextPlain.Mime, a, []byte("EHLO world")) if h != nil { t.Errorf("Expected nil-data, got %x", h) } @@ -290,7 +284,7 @@ func signDataPlain(t *testing.T) { } control <- "Y" control <- "a_long_password" - h, err = api.SignData(context.Background(), DataPlain.Mime, a, []byte("EHLO world")) + h, err = api.SignData(context.Background(), TextPlain.Mime, a, []byte("EHLO world")) if err != nil { t.Fatal(err) } @@ -299,22 +293,22 @@ func signDataPlain(t *testing.T) { } } -func signDataStructured(t *testing.T) { +func signTypedData(t *testing.T) { // TODO } func TestSignData(t *testing.T) { // application/validator or `0x00` - signApplicationValidator(t) + signTextValidator(t) - // application/clique or `0x01` + // data/structured `0x01` + signTypedData(t) + + // application/clique or `0x02` signApplicationClique(t) - // data/plain or `0x45` - signDataPlain(t) - - // data/structured `0x46` - signDataStructured(t) + // text/plain or `0x45` + signTextPlain(t) } func mkTestTx(from common.MixedcaseAddress) SendTxArgs { diff --git a/signer/core/apiv2.go b/signer/core/signed_data.go similarity index 54% rename from signer/core/apiv2.go rename to signer/core/signed_data.go index 8c87ebd844..79e49989ac 100644 --- a/signer/core/apiv2.go +++ b/signer/core/signed_data.go @@ -4,30 +4,39 @@ import ( "bytes" "context" "encoding/hex" + "errors" "fmt" + "math/big" + "math/rand" + "reflect" + "sort" + "strings" + "time" + "unicode" + + "github.com/PaulRBerg/basics/helpers" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" - "math/big" - "sort" - "strings" - "unicode" ) type TypedData struct { - Types EIP712Types `json:"types"` - PrimaryType string `json:"primaryType"` - Domain EIP712Domain `json:"domain"` - Message EIP712Message `json:"message"` + Types map[string]EIP712Type `json:"types"` + PrimaryType string `json:"primaryType"` + Domain EIP712Domain `json:"domain"` + Message EIP712Message `json:"message"` } -type EIP712Types map[string][]map[string]string +type EIP712Type []map[string]string type EIP712TypePriority struct { Type string Value uint } +type EIP712Data = map[string]interface{} + type EIP712Domain struct { Name string `json:"name"` Version string `json:"version"` @@ -38,54 +47,45 @@ type EIP712Domain struct { type EIP712Message map[string]interface{} -// Typed data according to EIP712 -// +// SignTypedData signs EIP712 conformant typed data // hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}") func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) { if err := data.Domain.IsValid(); err != nil { return nil, err } if data.PrimaryType == "" { - return nil, fmt.Errorf("primary type undefined") + return nil, errors.New("primary type undefined") } - domainTypes := EIP712Types{ + domainTypes := map[string]EIP712Type{ "EIP712Domain": data.Types["EIP712Domain"], } - domainSeparator, err := hashStruct(domainTypes, data.Domain.Values(), "") - if err != nil { - return nil, err - } - + domainSeparator := hashStruct(domainTypes, data.PrimaryType, data.Domain.Values(), 0) + //if err != nil { + // return nil, err + //} delete(data.Types, "EIP712Domain") - typedDataHash, err := hashStruct(data.Types, data.Message, data.PrimaryType) - if err != nil { - return nil, err - } + typedDataHash := hashStruct(data.Types, data.PrimaryType, data.Message, 0) + //if err != nil { + // return nil, err + //} fmt.Println("domainSeparator", domainSeparator.String()) fmt.Println("typedDataHash", typedDataHash.String()) return common.FromHex("0xdeadbeef"), nil } +// hashStruct generates the following encoding for the given domain and message: // `encode(domainSeparator : 𝔹²⁵⁶, message : 𝕊) = "\x19\x01" ‖ domainSeparator ‖ hashStruct(message)` -func hashStruct(types EIP712Types, message EIP712Message, primaryType string) (common.Hash, error) { - if primaryType != "" { - if types[primaryType] == nil { - return common.Hash{}, fmt.Errorf("primaryType specified but undefined") - } - } +func hashStruct(types map[string]EIP712Type, key string, data EIP712Data, depth int) common.Hash { + helpers.PrintJson("hashStruct", map[string]interface{}{ + "depth": depth, + }) - typeEncoding, err := encodeType(types, primaryType) - if err != nil { - return common.Hash{}, err - } + typeEncoding := encodeType(types) typeHash := hex.EncodeToString(crypto.Keccak256([]byte(typeEncoding))) - dataEncoding, err := encodeData(message) - if err != nil { - return common.Hash{}, err - } + dataEncoding := encodeData(types, key, data, depth) dataHash := hex.EncodeToString(crypto.Keccak256([]byte(dataEncoding))) var buffer bytes.Buffer @@ -93,14 +93,22 @@ func hashStruct(types EIP712Types, message EIP712Message, primaryType string) (c buffer.WriteString(dataHash) hash := common.BytesToHash(crypto.Keccak256(buffer.Bytes())) - return hash, nil + if depth == 0 { + fmt.Printf("typeEncoding %s\n", typeEncoding) + fmt.Printf("dataEncoding %s\n", dataEncoding) + } + return hash } -// encodeType transforms the given types into an encoding of the form +// encodeType generates the followign encoding: // `name ‖ "(" ‖ member₁ ‖ "," ‖ member₂ ‖ "," ‖ … ‖ memberₙ ")"` // -// Each member is written as `type ‖ " " ‖ name` encodings cascade down and are sorted by name -func encodeType(types EIP712Types, primaryType string) (string, error) { +// each member is written as `type ‖ " " ‖ name` encodings cascade down and are sorted by name +func encodeType(types map[string]EIP712Type) string { + helpers.PrintJson("hashStruct", map[string]interface{}{ + "types": types, + }) + var priorities = make(map[string]uint) for key := range types { priorities[key] = 0 @@ -137,7 +145,7 @@ func encodeType(types EIP712Types, primaryType string) (string, error) { for _, typeObj := range typeArr { typeVal := typeObj["type"] if typeKey == typeVal { - return "", fmt.Errorf("type %s cannot reference itself", typeVal) + panic(fmt.Errorf("type %s cannot reference itself", typeVal)) } firstChar := []rune(typeVal)[0] @@ -148,14 +156,14 @@ func encodeType(types EIP712Types, primaryType string) (string, error) { update(typeKey, typeVal) } } else { - return "", fmt.Errorf("referenced type %s is undefined", typeVal) + panic(fmt.Errorf("referenced type %s is undefined", typeVal)) } } else { - if !types.IsStandardType(typeVal) { + if !isStandardType(typeVal) { if types[typeVal] != nil { - return "", fmt.Errorf("Custom type %s must be capitalized", typeVal) + panic(fmt.Errorf("Custom type %s must be capitalized", typeVal)) } else { - return "", fmt.Errorf("Unknown type %s", typeVal) + panic(fmt.Errorf("Unknown type %s", typeVal)) } } } @@ -164,7 +172,7 @@ func encodeType(types EIP712Types, primaryType string) (string, error) { typeValArr = []string{} } - sortedPriorities := types.SortByPriorityAndName(priorities) + sortedPriorities := sortByPriorityAndName(priorities) var buffer bytes.Buffer for _, priority := range sortedPriorities { typeKey := priority.Type @@ -184,15 +192,64 @@ func encodeType(types EIP712Types, primaryType string) (string, error) { buffer.WriteString(")") } - return buffer.String(), nil + return buffer.String() } -func encodeData(values EIP712Message) (string, error) { - return "", nil +// encodeData generates the following encoding: +// `enc(value₁) ‖ enc(value₂) ‖ … ‖ enc(valueₙ)` +// +// each encoded member is 32-byte long +func encodeData(types map[string]EIP712Type, key string, val interface{}, depth int) string { + helpers.PrintJson("hashStruct", map[string]interface{}{ + "key": key, + "val": val, + "depth": depth, + }) + + var buffer bytes.Buffer + + switch val.(type) { + case EIP712Data: + for mapKey, mapVal := range val.(EIP712Data) { + if reflect.TypeOf(mapVal) == reflect.TypeOf(EIP712Data{}) { + hash := hashStruct(types, mapKey, mapVal.(EIP712Data), depth+1) + buffer.WriteString(hash.String()) + } else { + str := encodeData(types, mapKey, mapVal, depth+1) + buffer.WriteString(str) + } + } + break + + case bool: + boolVal, _ := val.(bool) + var int64Val int64 + if boolVal { + int64Val = 1 + } + encodedVal := abi.U256(big.NewInt(int64Val)) + fmt.Printf("bool encoded value:", encodedVal) + buffer.Write(encodedVal) + break + + case string: + bytesVal := common.FromHex(val.(string)) + hash := common.BytesToHash(crypto.Keccak256(bytesVal)) + buffer.WriteString(hash.String()) + break + + default: + arr := [...]string{"(a)", "(b)", "(c)"} + rand.Seed(time.Now().UnixNano()) + buffer.WriteString(arr[rand.Intn(3)]) + break + } + + return buffer.String() } -// Checks if the given type is a standard type accepted by EIP-712 -func (types *EIP712Types) IsStandardType(typeStr string) bool { +// isStandardType checks if the given type is a EIP712 conformant type +func isStandardType(typeStr string) bool { standardTypes := []string{ "array", "address", @@ -210,9 +267,9 @@ func (types *EIP712Types) IsStandardType(typeStr string) bool { return false } -// Helper function to sort types by priority and name. Priority is calculated b +// sortByPriorityAndName is a helper function to sort types by priority and name. Priority is calculated b // based upon the number of references. -func (types *EIP712Types) SortByPriorityAndName(input map[string]uint) []EIP712TypePriority { +func sortByPriorityAndName(input map[string]uint) []EIP712TypePriority { var priorities []EIP712TypePriority for key, val := range input { priorities = append(priorities, EIP712TypePriority{key, val}) @@ -234,20 +291,22 @@ func (types *EIP712Types) SortByPriorityAndName(input map[string]uint) []EIP712T return priorities } -// Check if the given domain is valid, i.e. contains at least the minimum viable keys and values +// IsValid checks if the given domain is valid, i.e. contains at least +// the minimum viable keys and values func (domain *EIP712Domain) IsValid() error { if domain.ChainId == big.NewInt(0) { - return fmt.Errorf("chainId must be specified according to EIP-155") + return errors.New("chainId must be specified according to EIP-155") } - if domain.Name == "" && domain.Version == "" && len(domain.VerifyingContract) == 0 && len(domain.Salt) == 0 { - return fmt.Errorf("domain undefined") + if len(domain.Name) == 0 && len(domain.Version) == 0 && len(domain.VerifyingContract) == 0 && len(domain.Salt) == 0 { + return errors.New("domain undefined") } return nil } -// Helper function to return the values of a domain in the form of a golang map +// Values is a helper function to return the values of a domain as a map +// with arbitrary values func (domain *EIP712Domain) Values() map[string]interface{} { return map[string]interface{}{ "name": domain.Name,