diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log
index a38552de7f..652dc04ae8 100644
--- a/cmd/clef/audit.log
+++ b/cmd/clef/audit.log
@@ -203,3 +203,22 @@ t=2018-10-21T20:03:51+0100 lvl=info msg=Configured api=signer audit log=audit.lo
t=2018-10-21T20:03:53+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:55028\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66D76e6A80DC7D46d7EC1b79b15Dfa34c3C6eF21 [chksum ok]" data="{Types:map[EIP712Domain:[map[type:string name:name] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[type:Person name:from] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]]}"
t=2018-10-21T20:17:01+0100 lvl=info msg=Configured api=signer audit log=audit.log
t=2018-10-21T20:17:44+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:55221\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66D76e6A80DC7D46d7EC1b79b15Dfa34c3C6eF21 [chksum ok]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[type:address name:wallet]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}"
+t=2018-10-23T18:55:35+0100 lvl=info msg=Configured api=signer audit log=audit.log
+t=2018-10-23T19:36:14+0100 lvl=info msg=Configured api=signer audit log=audit.log
+t=2018-10-23T19:38:40+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:55416\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66D76e6A80DC7D46d7EC1b79b15Dfa34c3C6eF21 [chksum ok]" data="{Types:map[Person:[map[name:name type:string] map[type:address name:wallet]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]] EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}"
+t=2018-10-23T19:46:18+0100 lvl=info msg=Configured api=signer audit log=audit.log
+t=2018-10-23T19:46:45+0100 lvl=info msg=Configured api=signer audit log=audit.log
+t=2018-10-23T19:46:48+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:55481\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66D76e6A80DC7D46d7EC1b79b15Dfa34c3C6eF21 [chksum ok]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC Salt:0x} Message:map[from:map[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}"
+t=2018-10-23T19:47:53+0100 lvl=info msg=Configured api=signer audit log=audit.log
+t=2018-10-23T19:48:10+0100 lvl=info msg=Configured api=signer audit log=audit.log
+t=2018-10-23T19:48:14+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:55506\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66D76e6A80DC7D46d7EC1b79b15Dfa34c3C6eF21 [chksum ok]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC Salt:0x} Message:map[to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}"
+t=2018-10-23T19:48:57+0100 lvl=info msg=Configured api=signer audit log=audit.log
+t=2018-10-23T19:49:10+0100 lvl=info msg=Configured api=signer audit log=audit.log
+t=2018-10-23T19:49:16+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:55529\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66D76e6A80DC7D46d7EC1b79b15Dfa34c3C6eF21 [chksum ok]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[type:Person name:to] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC Salt:0x} Message:map[contents:Hello, Bob! from:map[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]]}"
+t=2018-10-23T19:49:49+0100 lvl=info msg=Configured api=signer audit log=audit.log
+t=2018-10-23T19:49:57+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:55542\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66D76e6A80DC7D46d7EC1b79b15Dfa34c3C6eF21 [chksum ok]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}"
+t=2018-10-23T19:51:44+0100 lvl=info msg=Configured api=signer audit log=audit.log
+t=2018-10-23T19:51:57+0100 lvl=info msg=Configured api=signer audit log=audit.log
+t=2018-10-23T19:52:05+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:55568\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66D76e6A80DC7D46d7EC1b79b15Dfa34c3C6eF21 [chksum ok]" data="{Types:map[Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]] EIP712Domain:[map[type:string name:name] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC Salt:0x} Message:map[contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]]}"
+t=2018-10-23T20:12:04+0100 lvl=info msg=Configured api=signer audit log=audit.log
+t=2018-10-23T20:12:14+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:55752\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66D76e6A80DC7D46d7EC1b79b15Dfa34c3C6eF21 [chksum ok]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[type:string name:contents]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}"
diff --git a/cmd/clef/main.go b/cmd/clef/main.go
index dca06e9a7e..0d56149590 100644
--- a/cmd/clef/main.go
+++ b/cmd/clef/main.go
@@ -330,7 +330,7 @@ func initialize(c *cli.Context) error {
// If using the stdioui, we can't do the 'confirm'-flow
fmt.Fprintf(logOutput, legalWarning)
} else {
- // Temporarily disabled while in development
+ // Temporarily disabled
//if !confirm(legalWarning) {
// return fmt.Errorf("aborted by user")
//}
@@ -556,7 +556,7 @@ func readMasterKey(ctx *cli.Context, ui core.SignerUI) ([]byte, error) {
var password string
// If ui is not nil, get the password from ui.
if ui != nil {
- // Temporarily disabled while in development
+ // Temporarily disabled
//resp, err := ui.OnInputRequired(core.UserInputRequest{
// Title: "Master Password",
// Prompt: "Please enter the password to decrypt the master seed",
diff --git a/signer/core/api_test.go b/signer/core/api_test.go
index f1695feec9..ff68c0b4e1 100644
--- a/signer/core/api_test.go
+++ b/signer/core/api_test.go
@@ -245,72 +245,6 @@ func TestNewAcc(t *testing.T) {
}
}
-func signTextValidator(t *testing.T) {
- // TODO
-}
-
-func signApplicationClique(t *testing.T) {
- // TODO
-}
-
-func signTextPlain(t *testing.T) {
- api, control := setup(t)
- //Create two accounts
- createAccount(control, api, t)
- createAccount(control, api, t)
- control <- "1"
- list, err := api.List(context.Background())
- if err != nil {
- t.Fatal(err)
- }
- a := common.NewMixedcaseAddress(list[0])
-
- control <- "Y"
- control <- "wrongpassword"
- h, err := api.SignData(context.Background(), TextPlain.Mime, a, []byte("EHLO world"))
- if h != nil {
- t.Errorf("Expected nil-data, got %x", h)
- }
- if err != keystore.ErrDecrypt {
- t.Errorf("Expected ErrLocked! %v", err)
- }
- control <- "No way"
- h, err = api.SignData(context.Background(), TextPlain.Mime, a, []byte("EHLO world"))
- if h != nil {
- t.Errorf("Expected nil-data, got %x", h)
- }
- if err != ErrRequestDenied {
- t.Errorf("Expected ErrRequestDenied! %v", err)
- }
- control <- "Y"
- control <- "a_long_password"
- h, err = api.SignData(context.Background(), TextPlain.Mime, a, []byte("EHLO world"))
- if err != nil {
- t.Fatal(err)
- }
- if h == nil || len(h) != 65 {
- t.Errorf("Expected 65 byte signature (got %d bytes)", len(h))
- }
-}
-
-func signTypedData(t *testing.T) {
- // TODO
-}
-
-func TestSignData(t *testing.T) {
- // application/validator or `0x00`
- signTextValidator(t)
-
- // data/structured `0x01`
- signTypedData(t)
-
- // application/clique or `0x02`
- signApplicationClique(t)
-
- // text/plain or `0x45`
- signTextPlain(t)
-}
-
func mkTestTx(from common.MixedcaseAddress) SendTxArgs {
to := common.NewMixedcaseAddress(common.HexToAddress("0x1337"))
gas := hexutil.Uint64(21000)
diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go
index bf12a3ba45..2b74cb62ec 100644
--- a/signer/core/signed_data.go
+++ b/signer/core/signed_data.go
@@ -1,17 +1,31 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see .
+//
package core
import (
"bytes"
"context"
- "encoding/hex"
"encoding/json"
"errors"
"fmt"
- "github.com/ethereum/go-ethereum/common/math"
"math/big"
"mime"
"reflect"
- "sort"
+ "strconv"
"strings"
"unicode"
@@ -44,11 +58,11 @@ type EIP712TypePriority struct {
type EIP712Data = map[string]interface{}
type EIP712Domain struct {
- Name string `json:"name"`
- Version string `json:"version"`
- ChainId *big.Int `json:"chainId"`
- VerifyingContract common.Address `json:"verifyingContract"`
- Salt hexutil.Bytes `json:"salt"`
+ Name string `json:"name"`
+ Version string `json:"version"`
+ ChainId *big.Int `json:"chainId"`
+ VerifyingContract string `json:"verifyingContract"`
+ Salt hexutil.Bytes `json:"salt"`
}
const (
@@ -208,43 +222,26 @@ func signCliqueHeader(header *types.Header) (hexutil.Bytes, error) {
return hash.Bytes(), nil
}
-// SignTypedData signs EIP712 conformant typed data
+// SignTypedData signs EIP-712 conformant typed data
// hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}")
func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, typedData TypedData) (hexutil.Bytes, error) {
- domainTypes := EIP712Types{
- "EIP712Domain": typedData.Types["EIP712Domain"],
- }
- domainSeparatorBytes := typedData.hashStruct(domainTypes, typedData.Domain.Map(), "EIP712Domain", 0)
- domainSeparator := common.BytesToHash(domainSeparatorBytes)
-
- domainlessTypes := make(EIP712Types)
- for typeKey, typeVal := range typedData.Types {
- if typeKey == "EIP712Domain" {
- continue
- }
- domainlessTypes[typeKey] = typeVal
- }
- typedDataHashBytes := typedData.hashStruct(domainlessTypes, typedData.Message, typedData.PrimaryType, 0)
- typedDataHash := common.BytesToHash(typedDataHashBytes)
-
+ domainSeparator := typedData.HashStruct("EIP712Domain", typedData.Domain.Map())
+ typedDataHash := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
typedDataJson, err := json.Marshal(typedData.Map())
if err != nil {
return nil, err
}
- printJson("SignTypedData", typedData.Map())
- fmt.Printf("domainSeparator: %s\n", domainSeparator.String())
- fmt.Printf("typedDataHash: %s\n\n", typedDataHash.String())
-
buffer := bytes.Buffer{}
buffer.WriteString("\x19")
- buffer.WriteString(fmt.Sprintf("\x19\\x%x", DataTyped.ByteVersion))
- buffer.Write(domainSeparator.Bytes())
- buffer.Write(typedDataHash.Bytes())
-
- msg := buffer.String()
- sighash := crypto.Keccak256(buffer.Bytes())
- req := &SignDataRequest{Rawdata: typedDataJson, Message: msg, Hash: sighash, ContentType: DataTyped.Mime}
-
+ buffer.WriteString("\x01")
+ buffer.WriteString(common.Bytes2Hex(domainSeparator))
+ buffer.WriteString(common.Bytes2Hex(typedDataHash))
+ req := &SignDataRequest{
+ Rawdata: typedDataJson,
+ Message: buffer.String(),
+ Hash: crypto.Keccak256(buffer.Bytes()),
+ ContentType: DataTyped.Mime,
+ }
signature, err := api.Sign(ctx, addr, req)
if err != nil {
api.UI.ShowError(err.Error())
@@ -255,199 +252,194 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd
// hashStruct generates the following encoding for the given domain and message:
// `encode(domainSeparator : 𝔹²⁵⁶, message : 𝕊) = "\x19\x01" ‖ domainSeparator ‖ hashStruct(message)`
-func (typedData *TypedData) hashStruct(_types EIP712Types, data EIP712Data, dataType string, depth int) []byte {
- typeEncoding := typedData.encodeType(_types)
- typeHash := hex.EncodeToString(crypto.Keccak256(typeEncoding))
-
- dataEncoding := typedData.encodeData(_types, data, dataType, depth)
- dataHash := hex.EncodeToString(crypto.Keccak256(dataEncoding))
-
- var buffer bytes.Buffer
- buffer.WriteString(typeHash)
- buffer.WriteString(dataHash)
- encoding := crypto.Keccak256(buffer.Bytes())
-
- if depth == 0 {
- fmt.Printf("typeEncoding %s\n", common.Bytes2Hex(typeEncoding))
- fmt.Printf("dataEncoding %s\n", common.Bytes2Hex(dataEncoding))
- }
-
- printJson("hashStruct", map[string]interface{}{
- "depth": depth,
- "encoding": buffer.String(),
- })
- return encoding
+func (typedData *TypedData) HashStruct(primaryType string, data EIP712Data) []byte {
+ return crypto.Keccak256(typedData.EncodeData(primaryType, data))
}
-// 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 (typedData *TypedData) encodeType(_types EIP712Types) []byte {
- var priorities = make(map[string]uint)
- for key := range _types {
- priorities[key] = 0
- }
-
- // Updates the priority for every new custom type discovered
- update := func(typeKey string, typeVal string) {
- priorities[typeVal]++
-
- // Importantly, we also have to check for parent types to increment them too
- for _, typeObj := range _types[typeVal] {
- _typeVal := typeObj["type"]
-
- firstChar := []rune(_typeVal)[0]
- if unicode.IsUpper(firstChar) {
- priorities[_typeVal]++
- }
- }
- }
-
- // Checks if referenced type has already been visited to optimise algo
- visited := func(arr []string, val string) bool {
+// dependencies returns an array of custom types ordered by their
+// hierarchical reference tree
+func (typedData *TypedData) Dependencies(primaryType string, found []string) []string {
+ includes := func(arr []string, str string) bool {
for _, obj := range arr {
- if obj == val {
+ if obj == str {
return true
}
}
return false
}
- for typeKey, typeArr := range _types {
- var typeValArr []string
- for _, typeObj := range typeArr {
- typeVal := typeObj["type"]
-
- // filtering the structs from the primitives
- if _types[typeVal] != nil && !visited(typeValArr, typeVal) {
- typeValArr = append(typeValArr, typeVal)
- update(typeKey, typeVal)
+ if includes(found, primaryType) {
+ return found
+ }
+ if typedData.Types[primaryType] == nil {
+ return found
+ }
+ found = append(found, primaryType)
+ for _, field := range typedData.Types[primaryType] {
+ for _, dep := range typedData.Dependencies(field["type"], found) {
+ if !includes(found, dep) {
+ found = append(found, dep)
}
}
- typeValArr = []string{}
}
+ return found
+}
- if _types[typedData.PrimaryType] != nil {
- priorities[typedData.PrimaryType] = math.MaxInt32
+// encodeType generates the following encoding:
+// `name ‖ "(" ‖ member₁ ‖ "," ‖ member₂ ‖ "," ‖ … ‖ memberₙ ")"`
+//
+// each member is written as `type ‖ " " ‖ name` encodings cascade down and are sorted by name
+func (typedData *TypedData) EncodeType(primaryType string) []byte {
+ // Get dependencies primary first, then alphabetical
+ deps := typedData.Dependencies(primaryType, []string{})
+ for i, dep := range deps {
+ if dep == primaryType {
+ deps = append(deps[:i], deps[i+1:]...)
+ break
+ }
}
+ deps = append([]string{primaryType}, deps...)
- sortedPriorities := sortByPriorityAndName(priorities)
+ // Format as a string with fields
var buffer bytes.Buffer
- for _, priority := range sortedPriorities {
- typeKey := priority.Type
- typeArr := _types[typeKey]
-
- buffer.WriteString(typeKey)
+ for _, dep := range deps {
+ buffer.WriteString(dep)
buffer.WriteString("(")
-
- for _, typeObj := range typeArr {
- buffer.WriteString(typeObj["type"])
+ for _, obj := range typedData.Types[dep] {
+ buffer.WriteString(obj["type"])
buffer.WriteString(" ")
- buffer.WriteString(typeObj["name"])
+ buffer.WriteString(obj["name"])
buffer.WriteString(",")
}
-
buffer.Truncate(buffer.Len() - 1)
buffer.WriteString(")")
}
-
- printJson("encodeType", map[string]interface{}{
- "types": _types,
- "encoding": buffer.String(),
- })
return buffer.Bytes()
}
+func (typedData *TypedData) TypeHash(primaryType string) []byte {
+ return crypto.Keccak256(typedData.EncodeType(primaryType))
+}
+
+func bytesValueOf(_interface interface{}) []byte {
+ bytesVal, ok := _interface.([]byte)
+ if ok {
+ return bytesVal
+ }
+
+ switch reflect.TypeOf(_interface) {
+ case reflect.TypeOf(string("")):
+ return []byte(_interface.(string))
+ break
+ default:
+ break
+ }
+
+ panic(fmt.Errorf("unrecognized interface %v", _interface))
+ return []byte{}
+}
+
// encodeData generates the following encoding:
// `enc(value₁) ‖ enc(value₂) ‖ … ‖ enc(valueₙ)`
//
// each encoded member is 32-byte long
-func (typedData *TypedData) encodeData(_types EIP712Types, data interface{}, dataType string, depth int) []byte {
- var buffer bytes.Buffer
+func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}) []byte {
+ encTypes := []string{}
+ encValues := []interface{}{}
- // TODO regex
- // handle arrays
- if strings.Contains(dataType, "[]") {
- arrayVal := data.([]interface{})
- dataType := "TODO"
+ // Add typehash
+ encTypes = append(encTypes, "bytes32")
+ encValues = append(encValues, typedData.TypeHash(primaryType))
- var arrayBuffer bytes.Buffer
- for obj := range arrayVal {
- objEncoding := typedData.encodeData(_types, obj, dataType, depth+1)
- arrayBuffer.Write(objEncoding)
- }
+ // Handle primitive values
+ handlePrimitiveValue := func(encType string, encValue interface{}) (string, interface{}) {
+ var primitiveEncType string
+ var primitiveEncValue interface{}
- encoding := arrayBuffer.Bytes()
- buffer.Write(encoding)
- return buffer.Bytes()
- }
-
- // handle maps
- firstChar := []rune(dataType)[0]
- if unicode.IsUpper(firstChar) {
- for mapKey, mapVal := range data.(EIP712Data) {
- nextDataType := findNextDataType(_types, dataType, mapKey)
- if reflect.TypeOf(mapVal) == reflect.TypeOf(EIP712Data{}) {
- data := mapVal.(map[string]interface{})
- encoding := typedData.hashStruct(_types, data, nextDataType, depth+1)
- buffer.Write(encoding)
- } else {
- encoding := typedData.encodeData(_types, mapVal, nextDataType, depth+1)
- buffer.Write(encoding)
+ switch encType {
+ case "address":
+ primitiveEncType = "address"
+ bytesValue := []byte{}
+ for i := 0; i < 12; i++ {
+ bytesValue = append(bytesValue, 0)
}
+ foo := common.BytesToAddress([]byte(encValue.(string)))
+ fmt.Println(foo)
+ for _, _byte := range common.BytesToAddress([]byte(encValue.(string))) {
+ bytesValue = append(bytesValue, _byte)
+ }
+ primitiveEncValue = bytesValue
+ break
+ case "bool":
+ primitiveEncType = "uint256"
+ var int64Val int64
+ if encValue.(bool) {
+ int64Val = 1
+ }
+ primitiveEncValue = abi.U256(big.NewInt(int64Val))
+ break
+ case "bytes", "string":
+ primitiveEncType = "bytes32"
+ primitiveEncValue = crypto.Keccak256(bytesValueOf(encValue))
+ break
+ default:
+ if strings.HasPrefix(encType, "bytes") {
+ encTypes = append(encTypes, "bytes32")
+ sizeStr := strings.TrimPrefix(encType, "bytes")
+ size, _ := strconv.Atoi(sizeStr)
+ bytesValue := []byte{}
+ for i := 0; i < 32-size; i++ {
+ bytesValue = append(bytesValue, 0)
+ }
+ for _, _byte := range encValue.([]byte) {
+ bytesValue = append(bytesValue, _byte)
+ }
+ primitiveEncValue = bytesValue
+ } else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") {
+ primitiveEncType = "uint256"
+ primitiveEncValue = abi.U256(encValue.(*big.Int))
+ }
+ break
}
- return buffer.Bytes()
+ return primitiveEncType, primitiveEncValue
}
- // TODO regex
- // handle bytes
- if strings.Contains(dataType, TypeBytes) {
- bytesVal := data.([]byte)
- encoding := crypto.Keccak256(bytesVal)
- buffer.Write(encoding)
- }
-
- // TODO regex
- // handle ints
- if strings.Contains(dataType, TypeInt) {
- encoding := abi.U256(data.(*big.Int)) // not sure if this is big endian order, but it's definitey sign extended to 256 bit because of using the U256 function
- buffer.Write(encoding)
- return buffer.Bytes()
- }
-
- // handle what's left
- switch dataType {
- case TypeAddress:
- addressVal, _ := data.(common.Address)
- encoding := addressVal.Bytes() // hopefully this means uint160 encoding?
- buffer.Write(encoding)
- break
- case TypeBool:
- boolVal, _ := data.(bool)
- var int64Val int64
- if boolVal {
- int64Val = 1
+ // Add field contents. Structs and arrays have special handlings.
+ for _, field := range typedData.Types[primaryType] {
+ encType := field["type"]
+ encValue := data[field["name"]]
+ if encType[len(encType)-1:] == "]" {
+ encTypes = append(encTypes, "bytes32")
+ parsedType := strings.Split(encType, "[")[0]
+ arrayBuffer := bytes.Buffer{}
+ for _, item := range encValue.([]interface{}) {
+ if typedData.Types[parsedType] != nil {
+ encoding := typedData.EncodeData(parsedType, item.(map[string]interface{}))
+ arrayBuffer.Write(encoding)
+ } else {
+ _, encValue := handlePrimitiveValue(encType, encValue)
+ arrayBuffer.Write(bytesValueOf(encValue))
+ }
+ }
+ encValues = append(encValues, crypto.Keccak256(arrayBuffer.Bytes()))
+ } else if typedData.Types[field["type"]] != nil {
+ encTypes = append(encTypes, "bytes32")
+ mapValue := encValue.(map[string]interface{})
+ encValue = crypto.Keccak256(typedData.EncodeData(field["type"], mapValue))
+ encValues = append(encValues, encValue)
+ } else {
+ primitiveEncType, primitiveEncValue := handlePrimitiveValue(encType, encValue)
+ encTypes = append(encTypes, primitiveEncType)
+ encValues = append(encValues, primitiveEncValue)
}
- encoding := abi.U256(big.NewInt(int64Val))
- buffer.Write(encoding)
- break
- case TypeString:
- bytesVal := common.FromHex(data.(string))
- encoding := crypto.Keccak256(bytesVal)
- buffer.Write(encoding)
- break
- default:
- break
}
- printJson("encodeData", map[string]interface{}{
- "dataType": dataType,
- "data": data,
- "depth": depth,
- "encoding": buffer.String(),
- })
- return buffer.Bytes()
+ buffer := bytes.Buffer{}
+ for _, encValue := range encValues {
+ buffer.Write(bytesValueOf(encValue))
+ }
+
+ return buffer.Bytes() // https://github.com/ethereumjs/ethereumjs-abi/blob/master/lib/index.js#L336
}
// Determines the content type and then recovers the address associated with the given sig
@@ -488,39 +480,6 @@ func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data he
}
}
-// sortByPriorityAndName is a helper function to sort types by priority and name. Priority is calculated b
-// based upon the number of references.
-func sortByPriorityAndName(input map[string]uint) []EIP712TypePriority {
- var priorities []EIP712TypePriority
- for key, val := range input {
- priorities = append(priorities, EIP712TypePriority{key, val})
- }
- // Alphabetically
- sort.Slice(priorities, func(i, j int) bool {
- return priorities[i].Type < priorities[j].Type
- })
- // Priority
- sort.Slice(priorities, func(i, j int) bool {
- return priorities[i].Value > priorities[j].Value
- })
-
- return priorities
-}
-
-// findNextDataType
-// blah blah
-func findNextDataType(_types EIP712Types, mapType string, mapKey string) string {
- eip712type := _types[mapType]
-
- for _, mapObj := range eip712type {
- if mapObj["name"] == mapKey {
- return mapObj["type"]
- }
- }
-
- return ""
-}
-
// UnmarshalJSON validates the input data
func (typedData *TypedData) UnmarshalJSON(data []byte) error {
type input struct {
@@ -591,6 +550,7 @@ func (types *EIP712Types) IsValid() error {
return fmt.Errorf("referenced type %s is undefined", typeVal)
}
} else {
+ // TODO: better type checking
if !isStandardTypeStr(typeVal) {
if (*types)[typeVal] != nil {
return fmt.Errorf("custom type %s must be capitalized", typeVal)
diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go
new file mode 100644
index 0000000000..58502d0080
--- /dev/null
+++ b/signer/core/signed_data_test.go
@@ -0,0 +1,190 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see .
+//
+package core
+
+import (
+ "context"
+ "fmt"
+ "github.com/ethereum/go-ethereum/accounts/keystore"
+ "github.com/ethereum/go-ethereum/common"
+ "math/big"
+ "testing"
+)
+
+var typesStandard = EIP712Types{
+ "EIP712Domain": {
+ {
+ "name": "name",
+ "type": "string",
+ },
+ {
+ "name": "version",
+ "type": "string",
+ },
+ {
+ "name": "chainId",
+ "type": "uint256",
+ },
+ {
+ "name": "verifyingContract",
+ "type": "address",
+ },
+ },
+ "Person": {
+ {
+ "name": "name",
+ "type": "string",
+ },
+ {
+ "name": "wallet",
+ "type": "address",
+ },
+ },
+ "Mail": {
+ {
+ "name": "from",
+ "type": "Person",
+ },
+ {
+ "name": "to",
+ "type": "Person",
+ },
+ {
+ "name": "contents",
+ "type": "string",
+ },
+ },
+}
+
+const primaryType = "Mail"
+
+var domainStandard = EIP712Domain{
+ "Ether Mail",
+ "1",
+ big.NewInt(1),
+ "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
+ nil,
+}
+
+var dataStandard = map[string]interface{}{
+ "from": map[string]interface{}{
+ "name": "Cow",
+ "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
+ },
+ "to": map[string]interface{}{
+ "name": "Bob",
+ "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
+ },
+ "contents": "Hello, Bob!",
+}
+
+var typedData = TypedData{
+ typesStandard,
+ primaryType,
+ domainStandard,
+ dataStandard,
+}
+
+func TestSignData(t *testing.T) {
+ api, control := setup(t)
+ //Create two accounts
+ createAccount(control, api, t)
+ createAccount(control, api, t)
+ control <- "1"
+ list, err := api.List(context.Background())
+ if err != nil {
+ t.Fatal(err)
+ }
+ a := common.NewMixedcaseAddress(list[0])
+
+ control <- "Y"
+ control <- "wrongpassword"
+ signature, err := api.SignData(context.Background(), TextPlain.Mime, a, []byte("EHLO world"))
+ if signature != nil {
+ t.Errorf("Expected nil-data, got %x", signature)
+ }
+ if err != keystore.ErrDecrypt {
+ t.Errorf("Expected ErrLocked! %v", err)
+ }
+ control <- "No way"
+ signature, err = api.SignData(context.Background(), TextPlain.Mime, a, []byte("EHLO world"))
+ if signature != nil {
+ t.Errorf("Expected nil-data, got %x", signature)
+ }
+ if err != ErrRequestDenied {
+ t.Errorf("Expected ErrRequestDenied! %v", err)
+ }
+ // text/plain
+ control <- "Y"
+ control <- "a_long_password"
+ signature, err = api.SignData(context.Background(), TextPlain.Mime, a, []byte("EHLO world"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if signature == nil || len(signature) != 65 {
+ t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
+ }
+ // data/typed
+ control <- "Y"
+ control <- "a_long_password"
+ signature, err = api.SignTypedData(context.Background(), a, typedData)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if signature == nil || len(signature) != 65 {
+ t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
+ }
+ // TODO: test signature r,s,v values
+}
+
+func TestHashStruct(t *testing.T) {
+ mainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(typedData.HashStruct(typedData.PrimaryType, typedData.Message)))
+ if mainHash != "0xc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e" {
+ t.Fatal(fmt.Errorf("hashStruct result %s is incorrect", mainHash))
+ }
+
+ domainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(typedData.HashStruct("EIP712Domain", typedData.Domain.Map())))
+ if domainHash != "0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f" {
+ t.Fatal(fmt.Errorf("hashStruct result %s is incorrect", domainHash))
+ }
+}
+
+func TestEncodeType(t *testing.T) {
+ domainTypeEncoding := string(typedData.EncodeType("EIP712Domain"))
+ if domainTypeEncoding != "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" {
+ t.Fatal(fmt.Errorf("encodeType result %s is incorrect", domainTypeEncoding))
+ }
+
+ mailTypeEncoding := string(typedData.EncodeType(typedData.PrimaryType))
+ if mailTypeEncoding != "Mail(Person from,Person to,string contents)Person(string name,address wallet)" {
+ t.Fatal(fmt.Errorf("encodeType result %s is incorrect", mailTypeEncoding))
+ }
+}
+
+func TestTypeHash(t *testing.T) {
+ mailTypeHash := fmt.Sprintf("0x%s", common.Bytes2Hex(typedData.TypeHash(typedData.PrimaryType)))
+ if mailTypeHash != "0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2" {
+ t.Fatal(fmt.Errorf("typeHash result %s is incorrect", mailTypeHash))
+ }
+}
+
+func TestEncodeData(t *testing.T) {
+ dataEncoding := fmt.Sprintf("0x%s", common.Bytes2Hex(typedData.EncodeData(typedData.PrimaryType, typedData.Message)))
+ if dataEncoding != "0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8cd54f074a4af31b4411ff6a60c9719dbd559c221c8ac3492d9d872b041d703d1b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8" {
+ t.Fatal(fmt.Errorf("encodeData result %s is incorrect", dataEncoding))
+ }
+}
\ No newline at end of file