Fixed bugs in 'data/typed' signs

This commit is contained in:
Paul Berg 2018-11-02 12:03:14 +01:00
parent 05314acf9a
commit 4b893bee79
5 changed files with 59 additions and 160 deletions

View file

@ -402,3 +402,5 @@ t=2018-11-01T22:23:54+0100 lvl=info msg=Configured api=signer audit log=audit.lo
t=2018-11-01T22:23:56+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60509\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0xd090B1504cC9341BC18cE5d34D750545bb515FdC message:0xcafebabe]" content-type=text/validator t=2018-11-01T22:23:56+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60509\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0xd090B1504cC9341BC18cE5d34D750545bb515FdC message:0xcafebabe]" content-type=text/validator
t=2018-11-01T22:36:15+0100 lvl=info msg=Configured api=signer audit log=audit.log t=2018-11-01T22:36:15+0100 lvl=info msg=Configured api=signer audit log=audit.log
t=2018-11-01T22:38:04+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60761\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0xE85E8fceAce32f641595bcAf5e16aba14667991D message:0xcafebabe]" content-type=text/validator t=2018-11-01T22:38:04+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60761\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0xE85E8fceAce32f641595bcAf5e16aba14667991D message:0xcafebabe]" content-type=text/validator
t=2018-11-02T11:46:08+0100 lvl=info msg=Configured api=signer audit log=audit.log
t=2018-11-02T11:46:14+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:49691\",\"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[type:string name:name] 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:} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}"

View file

@ -48,10 +48,10 @@ type ExternalAPI interface {
SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error)
// SignData - request to sign the given data (plus prefix) // SignData - request to sign the given data (plus prefix)
SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error)
// SignStructuredData - request to sign the given structured data (plus prefix) // SignTypedData - request to sign the given structured data (plus prefix)
//SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error)
// EcRecover - recover public key from given message and signature // EcRecover - recover public key from given message and signature
EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error)
// Export - request to export an account // Export - request to export an account
Export(ctx context.Context, addr common.Address) (json.RawMessage, error) Export(ctx context.Context, addr common.Address) (json.RawMessage, error)
// Import - request to import an account // Import - request to import an account

View file

@ -70,18 +70,18 @@ func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr com
return b, e return b, e
} }
//func (l *AuditLogger) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) { func (l *AuditLogger) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) {
// l.log.Info("SignTypedData", "type", "request", "metadata", MetadataFromContext(ctx).String(), l.log.Info("SignTypedData", "type", "request", "metadata", MetadataFromContext(ctx).String(),
// "addr", addr.String(), "data", data) "addr", addr.String(), "data", data)
// b, e := l.api.SignTypedData(ctx, addr, data) b, e := l.api.SignTypedData(ctx, addr, data)
// l.log.Info("SignTypedData", "type", "response", "data", common.Bytes2Hex(b), "error", e) l.log.Info("SignTypedData", "type", "response", "data", common.Bytes2Hex(b), "error", e)
// return b, e return b, e
//} }
func (l *AuditLogger) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { func (l *AuditLogger) EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) {
l.log.Info("EcRecover", "type", "request", "metadata", MetadataFromContext(ctx).String(), l.log.Info("EcRecover", "type", "request", "metadata", MetadataFromContext(ctx).String(),
"data", common.Bytes2Hex(data), "sig", common.Bytes2Hex(sig), "content-type", contentType) "data", common.Bytes2Hex(data), "sig", common.Bytes2Hex(sig))
b, e := l.api.EcRecover(ctx, contentType, data, sig) b, e := l.api.EcRecover(ctx, data, sig)
l.log.Info("EcRecover", "type", "response", "address", b.String(), "error", e) l.log.Info("EcRecover", "type", "response", "address", b.String(), "error", e)
return b, e return b, e
} }

View file

@ -19,6 +19,7 @@ package core
import ( import (
"bytes" "bytes"
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
@ -173,19 +174,8 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M
return nil, err return nil, err
} }
sighash, msg := SignTextValidator(validatorData) sighash, msg := SignTextValidator(validatorData)
fmt.Printf("sighash:%s", sighash)
req = &SignDataRequest{Rawdata: validatorData, Message: msg, Hash: sighash, ContentType: mediaType} req = &SignDataRequest{Rawdata: validatorData, Message: msg, Hash: sighash, ContentType: mediaType}
break break
case DataTyped.Mime:
// Signs EIP-712 conformant typed data
// hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}")
typedData, err := UnmarshalTypedData(data)
if err != nil {
return nil, err
}
sighash, msg := SignDataTyped(typedData)
req = &SignDataRequest{Rawdata: typedData, Message: msg, Hash: sighash, ContentType: mediaType}
break
case ApplicationClique.Mime: case ApplicationClique.Mime:
// Clique is the Ethereum PoA standard // Clique is the Ethereum PoA standard
cliqueData, err := hexutil.Decode(data.(string)) cliqueData, err := hexutil.Decode(data.(string))
@ -222,7 +212,6 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M
// SignTextWithValidator signs the given message which can be further recovered // SignTextWithValidator signs the given message which can be further recovered
// with the given validator. // with the given validator.
//
// hash = keccak256("\x19\x00"${address}${data}). // hash = keccak256("\x19\x00"${address}${data}).
func SignTextValidator(validatorData ValidatorData) (hexutil.Bytes, string) { func SignTextValidator(validatorData ValidatorData) (hexutil.Bytes, string) {
msg := fmt.Sprintf("\x19\x00%s%s", string(validatorData.Address.Bytes()), string(validatorData.Message)) msg := fmt.Sprintf("\x19\x00%s%s", string(validatorData.Address.Bytes()), string(validatorData.Message))
@ -267,51 +256,33 @@ func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) {
// SignTextPlain is a helper function that calculates a hash for the given message that can be // SignTextPlain is a helper function that calculates a hash for the given message that can be
// safely used to calculate a signature from. This gives context to the signed message and prevents // safely used to calculate a signature from. This gives context to the signed message and prevents
// signing of transactions. // signing of transactions.
//
// hash = keccak256("\x19$Ethereum Signed Message:\n"${message length}${message}). // hash = keccak256("\x19$Ethereum Signed Message:\n"${message length}${message}).
func SignTextPlain(data hexutil.Bytes) (hexutil.Bytes, string) { func SignTextPlain(data hexutil.Bytes) (hexutil.Bytes, string) {
// The letter `E` is \x45 in hex, retrofitting // The letter `E` is \x45 in hex, retrofitting
// https://github.com/ethereum/go-ethereum/pull/2940/commits // https://github.com/ethereum/go-ethereum/pull/2940/commits
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data))
return crypto.Keccak256([]byte(msg)), msg return crypto.Keccak256([]byte(msg)), msg
} }
// SignDataTyped signs EIP-712 conformant typed data
// hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}")
func SignDataTyped(typedData TypedData) (hexutil.Bytes, string) {
domainSeparator := typedData.HashStruct("EIP712Domain", typedData.Domain.Map())
typedDataHash := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
msg := fmt.Sprintf("\x19\x01%s%s", common.Bytes2Hex(domainSeparator), common.Bytes2Hex(typedDataHash))
return crypto.Keccak256(common.Hex2Bytes(msg)), msg
}
// SignTypedData signs EIP-712 conformant typed data // SignTypedData signs EIP-712 conformant typed data
// hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}") // hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}")
//func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, typedData TypedData) (hexutil.Bytes, error) { func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, typedData TypedData) (hexutil.Bytes, error) {
// domainSeparator := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) domainSeparator := typedData.HashStruct("EIP712Domain", typedData.Domain.Map())
// typedDataHash := typedData.HashStruct(typedData.PrimaryType, typedData.Message) typedDataHash := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
// typedDataJson, err := json.Marshal(typedData.Map()) _, err := json.Marshal(typedData.Map())
// if err != nil { if err != nil {
// return nil, err return nil, err
// } }
// buffer := bytes.Buffer{} msg := fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash))
// buffer.WriteString("\x19") sighash := crypto.Keccak256([]byte(msg))
// buffer.WriteString("\x01") req := &SignDataRequest{Rawdata: typedData.Map(), Message: msg, Hash: sighash, ContentType: DataTyped.Mime}
// buffer.WriteString(common.Bytes2Hex(domainSeparator)) signature, err := api.Sign(ctx, addr, req)
// buffer.WriteString(common.Bytes2Hex(typedDataHash)) if err != nil {
// req := &SignDataRequest{ api.UI.ShowError(err.Error())
// Rawdata: typedDataJson, return nil, err
// Message: buffer.String(), }
// Hash: crypto.Keccak256(buffer.Bytes()), return signature, nil
// ContentType: DataTyped.Mime, }
// }
// signature, err := api.Sign(ctx, addr, req)
// if err != nil {
// api.UI.ShowError(err.Error())
// return nil, err
// }
// return signature, nil
//}
// HashStruct generates the following encoding for the given domain and message: // HashStruct generates the following encoding for the given domain and message:
// `encode(domainSeparator : 𝔹²⁵⁶, message : 𝕊) = "\x19\x01" ‖ domainSeparator ‖ hashStruct(message)` // `encode(domainSeparator : 𝔹²⁵⁶, message : 𝕊) = "\x19\x01" ‖ domainSeparator ‖ hashStruct(message)`
@ -319,7 +290,7 @@ func (typedData *TypedData) HashStruct(primaryType string, data EIP712Data) hexu
return crypto.Keccak256(typedData.EncodeData(primaryType, data)) return crypto.Keccak256(typedData.EncodeData(primaryType, data))
} }
// dependencies returns an array of custom types ordered by their hierarchical reference tree // Dependencies returns an array of custom types ordered by their hierarchical reference tree
func (typedData *TypedData) Dependencies(primaryType string, found []string) []string { func (typedData *TypedData) Dependencies(primaryType string, found []string) []string {
includes := func(arr []string, str string) bool { includes := func(arr []string, str string) bool {
for _, obj := range arr { for _, obj := range arr {
@ -347,7 +318,7 @@ func (typedData *TypedData) Dependencies(primaryType string, found []string) []s
return found return found
} }
// encodeType generates the following encoding: // EncodeType generates the following encoding:
// `name ‖ "(" ‖ member₁ ‖ "," ‖ member₂ ‖ "," ‖ … ‖ memberₙ ")"` // `name ‖ "(" ‖ member₁ ‖ "," ‖ member₂ ‖ "," ‖ … ‖ memberₙ ")"`
// //
// each member is written as `type ‖ " " ‖ name` encodings cascade down and are sorted by name // each member is written as `type ‖ " " ‖ name` encodings cascade down and are sorted by name
@ -496,7 +467,7 @@ func bytesValueOf(_interface interface{}) hexutil.Bytes {
case reflect.TypeOf([]uint8{}): case reflect.TypeOf([]uint8{}):
return _interface.([]uint8) return _interface.([]uint8)
case reflect.TypeOf(string("")): case reflect.TypeOf(string("")):
return common.Hex2Bytes(_interface.(string)) return hexutil.Bytes(_interface.(string))
default: default:
break break
} }
@ -507,7 +478,7 @@ func bytesValueOf(_interface interface{}) hexutil.Bytes {
// EcRecover recovers the address associated with the given sig. // EcRecover recovers the address associated with the given sig.
// Only compatible with `text/plain` // Only compatible with `text/plain`
func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { func (api *SignerAPI) EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) {
// Returns the address for the Account that was used to create the signature. // Returns the address for the Account that was used to create the signature.
// //
// Note, this function is compatible with eth_sign and personal_sign. As such it recovers // Note, this function is compatible with eth_sign and personal_sign. As such it recovers
@ -562,74 +533,6 @@ func UnmarshalValidatorData(data interface{}) (ValidatorData, error) {
}, nil }, nil
} }
// UnmarshalTypedData converts the bytes input to typed data
func UnmarshalTypedData(data interface{}) (TypedData, error) {
raw := data.(map[string]interface{})
var _types, ok = raw["types"].(EIP712Types)
if !ok || _types == nil {
return TypedData{}, errors.New("types are undefined")
}
if err := _types.IsValid(); err != nil {
return TypedData{}, err
}
if _types["EIP712Domain"] == nil {
return TypedData{}, errors.New("domain types are undefined")
}
domain, err := UnmarshalDomain(data)
if err != nil {
return TypedData{}, err
}
primaryType, ok := raw["primaryType"].(string)
if !ok || len(primaryType) == 0 {
return TypedData{}, errors.New("primary type is undefined")
}
message, ok := raw["message"].(EIP712Data)
if !ok || message == nil {
return TypedData{}, errors.New("message is undefined")
}
return TypedData{
Types: _types,
PrimaryType: primaryType,
Domain: domain,
Message: message,
}, nil
}
// UnmarshalDomain converts the bytes input to a domain
func UnmarshalDomain(data interface{}) (EIP712Domain, error) {
raw := data.(map[string]interface{})["domain"].(map[string]interface{})
chainId := raw["chainId"].(*big.Int)
if chainId == big.NewInt(0) {
return EIP712Domain{}, errors.New("chainId must be specified according to EIP-155")
}
name, nameOk := raw["name"].(string)
version, versionOk := raw["version"].(string)
verifyingContract, verifyingContractOk := raw["verifyingContract"].(string)
salt, saltOk := raw["salt"].(string)
if (!nameOk || len(name) == 0) &&
(!versionOk || len(version) == 0) &&
(!verifyingContractOk || len(verifyingContract) == 0) &&
(!saltOk || len(salt) == 0) {
return EIP712Domain{}, errors.New("domain is undefined")
}
return EIP712Domain{
Name: name,
Version: version,
ChainId: chainId,
VerifyingContract: verifyingContract,
Salt: salt,
}, nil
}
// Map is a helper function to generate a map version of the typed data // Map is a helper function to generate a map version of the typed data
func (typedData *TypedData) Map() map[string]interface{} { func (typedData *TypedData) Map() map[string]interface{} {
dataMap := map[string]interface{}{ dataMap := map[string]interface{}{
@ -712,7 +615,7 @@ func (domain *EIP712Domain) IsValid() error {
} }
if len(domain.Name) == 0 && len(domain.Version) == 0 && len(domain.VerifyingContract) == 0 && len(domain.Salt) == 0 { if len(domain.Name) == 0 && len(domain.Version) == 0 && len(domain.VerifyingContract) == 0 && len(domain.Salt) == 0 {
return errors.New("domain undefined") return errors.New("domain is undefined")
} }
return nil return nil

View file

@ -19,11 +19,12 @@ package core
import ( import (
"context" "context"
"fmt" "fmt"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"math/big"
"testing"
) )
var typesStandard = EIP712Types{ var typesStandard = EIP712Types{
@ -100,13 +101,6 @@ var typedData = TypedData{
Message: messageStandard, Message: messageStandard,
} }
//var typedDataMap = map[string]interface{}{
// "types": typesStandard,
// "primaryType": primaryType,
// "domain": domainStandard,
// "message": messageStandard,
//}
func TestSignData(t *testing.T) { func TestSignData(t *testing.T) {
api, control := setup(t) api, control := setup(t)
//Create two accounts //Create two accounts
@ -149,7 +143,7 @@ func TestSignData(t *testing.T) {
// data/typed // data/typed
control <- "Y" control <- "Y"
control <- "a_long_password" control <- "a_long_password"
signature, err = api.SignData(context.Background(), DataTyped.Mime, a, typedData.Map()) signature, err = api.SignTypedData(context.Background(), a, typedData)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }