Drafted first version of EIP-712, including tests

This commit is contained in:
Paul Berg 2018-10-23 20:17:36 +01:00 committed by Martin Holst Swende
parent a618d3706f
commit 0484d96325
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
5 changed files with 394 additions and 291 deletions

View file

@ -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!]}"

View file

@ -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",

View file

@ -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)

View file

@ -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 <http://www.gnu.org/licenses/>.
//
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"
@ -47,7 +61,7 @@ type EIP712Domain struct {
Name string `json:"name"`
Version string `json:"version"`
ChainId *big.Int `json:"chainId"`
VerifyingContract common.Address `json:"verifyingContract"`
VerifyingContract string `json:"verifyingContract"`
Salt hexutil.Bytes `json:"salt"`
}
@ -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))
func (typedData *TypedData) HashStruct(primaryType string, data EIP712Data) []byte {
return crypto.Keccak256(typedData.EncodeData(primaryType, data))
}
printJson("hashStruct", map[string]interface{}{
"depth": depth,
"encoding": buffer.String(),
})
return encoding
}
// 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()
switch encType {
case "address":
primitiveEncType = "address"
bytesValue := []byte{}
for i := 0; i < 12; i++ {
bytesValue = append(bytesValue, 0)
}
// 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)
foo := common.BytesToAddress([]byte(encValue.(string)))
fmt.Println(foo)
for _, _byte := range common.BytesToAddress([]byte(encValue.(string))) {
bytesValue = append(bytesValue, _byte)
}
}
return buffer.Bytes()
}
// 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)
primitiveEncValue = bytesValue
break
case TypeBool:
boolVal, _ := data.(bool)
case "bool":
primitiveEncType = "uint256"
var int64Val int64
if boolVal {
if encValue.(bool) {
int64Val = 1
}
encoding := abi.U256(big.NewInt(int64Val))
buffer.Write(encoding)
primitiveEncValue = abi.U256(big.NewInt(int64Val))
break
case TypeString:
bytesVal := common.FromHex(data.(string))
encoding := crypto.Keccak256(bytesVal)
buffer.Write(encoding)
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 primitiveEncType, primitiveEncValue
}
printJson("encodeData", map[string]interface{}{
"dataType": dataType,
"data": data,
"depth": depth,
"encoding": buffer.String(),
})
return buffer.Bytes()
// 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)
}
}
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)

View file

@ -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 <http://www.gnu.org/licenses/>.
//
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))
}
}