From 6ec2ee41981bb7b012aa62b98b338e7a7cc4bdc2 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sat, 29 Sep 2018 01:35:27 +0300 Subject: [PATCH 01/84] Named functions and defined a basic EIP191 content type list --- cmd/clef/main.go | 4 +- signer/core/api.go | 184 +++++++++++++++++++++++++++++++++++----- signer/core/api_test.go | 6 +- signer/core/auditlog.go | 10 +-- 4 files changed, 173 insertions(+), 31 deletions(-) diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 519d63b3c1ba..898a63829699 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -631,8 +631,8 @@ func testExternalUI(api *core.SignerAPI) { _, err = api.SignTransaction(ctx, core.SendTxArgs{From: common.MixedcaseAddress{}}, nil) checkErr("SignTransaction", err) - _, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304")) - checkErr("Sign", err) + _, err = api.SignData(ctx, "text/plain", common.MixedcaseAddress{}, common.Hex2Bytes("01020304")) + checkErr("SignData", err) _, err = api.List(ctx) checkErr("List", err) _, err = api.New(ctx) diff --git a/signer/core/api.go b/signer/core/api.go index e9a3357855b8..688b3ce0609f 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -21,8 +21,11 @@ import ( "encoding/json" "errors" "fmt" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto/sha3" "io/ioutil" "math/big" + "mime" "reflect" "github.com/ethereum/go-ethereum/accounts" @@ -47,8 +50,8 @@ type ExternalAPI interface { New(ctx context.Context) (accounts.Account, error) // SignTransaction request to sign the specified transaction SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) - // Sign - request to sign the given data (plus prefix) - Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) + // SignData - request to sign the given data (plus prefix) + SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) // Export - request to export an account Export(ctx context.Context, addr common.Address) (json.RawMessage, error) // Import - request to import an account @@ -170,11 +173,12 @@ type ( NewPassword string `json:"new_password"` } SignDataRequest struct { - Address common.MixedcaseAddress `json:"address"` - Rawdata hexutil.Bytes `json:"raw_data"` - Message string `json:"message"` - Hash hexutil.Bytes `json:"hash"` - Meta Metadata `json:"meta"` + ContentType string `json:"content_type"` + Address common.MixedcaseAddress `json:"address"` + Rawdata hexutil.Bytes `json:"raw_data"` + Message string `json:"message"` + Hash hexutil.Bytes `json:"hash"` + Meta Metadata `json:"meta"` } SignDataResponse struct { Approved bool `json:"approved"` @@ -510,22 +514,25 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth } -// Sign calculates an Ethereum ECDSA signature for: -// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) +// SignData signs the hash of the provided data, but does so differently +// depending on the content-type specified. +// +// Depending on the content-type, different types of validations will occur. // // Note, the produced signature conforms to the secp256k1 curve R, S and V values, // where the V value will be 27 or 28 for legacy reasons. -// -// The key used to calculate the signature is decrypted with the given password. -// -// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign -func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { - sighash, msg := SignHash(data) +func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { + + var req, err = api.determineSignatureFormat(contentType, data) + if err != nil { + return nil, err + } + req.Address = addr + req.Meta = MetadataFromContext(ctx) + // We make the request prior to looking up if we actually have the account, to prevent // account-enumeration via the API - req := &SignDataRequest{Address: addr, Rawdata: data, Message: msg, Hash: sighash, Meta: MetadataFromContext(ctx)} res, err := api.UI.ApproveSignData(req) - if err != nil { return nil, err } @@ -538,8 +545,8 @@ func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, da if err != nil { return nil, err } - // Assemble sign the data with the wallet - signature, err := wallet.SignHashWithPassphrase(account, res.Password, sighash) + // Sign the data with the wallet + signature, err := wallet.SignHashWithPassphrase(account, res.Password, req.Hash) if err != nil { api.UI.ShowError(err.Error()) return nil, err @@ -548,14 +555,149 @@ func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, da return signature, nil } -// SignHash is a helper function that calculates a hash for the given message that can be +// EcRecover 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 +// the address of: +// hash = keccak256("\x19Ethereum Signed Message:\n"${message length}${message}) +// addr = ecrecover(hash, signature) +// +// Note, the signature must conform to the secp256k1 curve R, S and V values, where +// the V value must be be 27 or 28 for legacy reasons. +// +// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover +func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data, sig hexutil.Bytes) (common.Address, error) { + if len(sig) != 65 { + return common.Address{}, fmt.Errorf("signature must be 65 bytes long") + } + if sig[64] != 27 && sig[64] != 28 { + return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") + } + sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 + hash, _ := SignDataPlain(data) + rpk, err := crypto.SigToPub(hash, sig) + if err != nil { + return common.Address{}, err + } + return crypto.PubkeyToAddress(*rpk), nil +} + +// Determines which signature method should be used based upon the mime type +func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil.Bytes) (*SignDataRequest, error) { + var req *SignDataRequest + mediaType, _, err := mime.ParseMediaType(contentType) + if err != nil { + return nil, err + } + switch mediaType { + case "application/clique": + header := &types.Header{} + if err := rlp.DecodeBytes(data, header); err != nil { + return nil, err + } + sighash, err := SignCliqueHeader(header) + if err != nil { + return nil, err + } + msg := fmt.Sprintf("Clique block %d [0x%x]", header.Number, header.Hash()) + req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + case "application/validator": + // Sign calculates an Ethereum ECDSA signature for: + // keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) + + // In the cases where it matter ensure that the charset is handled. The charset + // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType + // charset, ok := params["charset"] + // As it is now, we accept any charset and just treat it as 'raw'. + + sighash, msg := DataWithValidatorHash(data) + req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + case "data/structured": + // EIP712 typed data + + // Sign calculates an Ethereum ECDSA signature for: + // keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) + + // In the cases where it matter ensure that the charset is handled. The charset + // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType + // charset, ok := params["charset"] + // As it is now, we accept any charset and just treat it as 'raw'. + + sighash, msg := DataStructuredHash(data) + req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + case "data/plain": + // Sign calculates an Ethereum ECDSA signature for: + // keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) + + // In the cases where it matter ensure that the charset is handled. The charset + // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType + // charset, ok := params["charset"] + // As it is now, we accept any charset and just treat it as 'raw'. + + sighash, msg := DataPlainHash(data) + req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + default: + return nil, fmt.Errorf("content type '%s' not implemented for signing", contentType) + } + return req, nil + +} + +// SignCliqueHeader returns the hash which is used as input for the proof-of-authority +// signing. It is the hash of the entire header apart from the 65 byte signature +// contained at the end of the extra data. +// +// The method requires the extra data to be at least 65 bytes -- the original implementation +// in clique.go panics if this is the case, thus it's been reimplemented here to avoid the panic +// and simply return an error instead +func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { + hash := common.Hash{} + if len(header.Extra) < 65 { + return hash.Bytes(), fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) + } + hasher := sha3.NewKeccak256() + rlp.Encode(hasher, []interface{}{ + header.ParentHash, + header.UncleHash, + header.Coinbase, + header.Root, + header.TxHash, + header.ReceiptHash, + header.Bloom, + header.Difficulty, + header.Number, + header.GasLimit, + header.GasUsed, + header.Time, + header.Extra[:len(header.Extra)-65], + header.MixDigest, + header.Nonce, + }) + hasher.Sum(hash[:0]) + return hash.Bytes(), nil +} + +// DataWithValidatorHash signs the given message according to EIP191. +// +// https://github.com/ethereum/EIPs/issues/712 +func DataWithValidatorHash(data []byte) ([]byte, string) { + return nil, "" +} + +// DataStructuredHash signs the given message according to EIP712. +// +// https://github.com/ethereum/EIPs/issues/712 +func DataStructuredHash(data []byte) ([]byte, string) { + return nil, "" +} + +// DataPlainHash is a helper function that calculates a hash for the given message that can be // safely used to calculate a signature from. // // The hash is calculated as // keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). // // This gives context to the signed message and prevents signing of transactions. -func SignHash(data []byte) ([]byte, string) { +func DataPlainHash(data []byte) ([]byte, string) { msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) return crypto.Keccak256([]byte(msg)), msg } diff --git a/signer/core/api_test.go b/signer/core/api_test.go index a8aa23896e6c..abe67c9f4d18 100644 --- a/signer/core/api_test.go +++ b/signer/core/api_test.go @@ -259,7 +259,7 @@ func TestSignData(t *testing.T) { control <- "Y" control <- "wrongpassword" - h, err := api.Sign(context.Background(), a, []byte("EHLO world")) + h, err := api.SignData(context.Background(), "text/plain", a, []byte("EHLO world")) if h != nil { t.Errorf("Expected nil-data, got %x", h) } @@ -267,7 +267,7 @@ func TestSignData(t *testing.T) { t.Errorf("Expected ErrLocked! %v", err) } control <- "No way" - h, err = api.Sign(context.Background(), a, []byte("EHLO world")) + h, err = api.SignData(context.Background(), "text/plain", a, []byte("EHLO world")) if h != nil { t.Errorf("Expected nil-data, got %x", h) } @@ -276,7 +276,7 @@ func TestSignData(t *testing.T) { } control <- "Y" control <- "a_long_password" - h, err = api.Sign(context.Background(), a, []byte("EHLO world")) + h, err = api.SignData(context.Background(), "text/plain", a, []byte("EHLO world")) if err != nil { t.Fatal(err) } diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go index 1f9c90918594..a0fb1b076706 100644 --- a/signer/core/auditlog.go +++ b/signer/core/auditlog.go @@ -63,11 +63,11 @@ func (l *AuditLogger) SignTransaction(ctx context.Context, args SendTxArgs, meth return res, e } -func (l *AuditLogger) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { - l.log.Info("Sign", "type", "request", "metadata", MetadataFromContext(ctx).String(), - "addr", addr.String(), "data", common.Bytes2Hex(data)) - b, e := l.api.Sign(ctx, addr, data) - l.log.Info("Sign", "type", "response", "data", common.Bytes2Hex(b), "error", e) +func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { + l.log.Info("SignData", "type", "request", "metadata", MetadataFromContext(ctx).String(), + "addr", addr.String(), "data", common.Bytes2Hex(data), "content-type", contentType) + b, e := l.api.SignData(ctx, contentType, addr, data) + l.log.Info("SignData", "type", "response", "data", common.Bytes2Hex(b), "error", e) return b, e } From 5314d8e9f0b419a3e38b27cb2b708937a97ca438 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sat, 29 Sep 2018 16:54:07 +0300 Subject: [PATCH 02/84] Written basic content type functions --- signer/core/api.go | 153 ++++++++++++++++++++++++++------------------- 1 file changed, 89 insertions(+), 64 deletions(-) diff --git a/signer/core/api.go b/signer/core/api.go index 688b3ce0609f..900392e822d5 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -108,6 +108,30 @@ type Metadata struct { Origin string `json:"Origin"` } +type SigFormat struct { + Mime string + ByteVersion byte +} + +var ( + ApplicationValidator = SigFormat{ + "application/validator", + 0x00, + } + ApplicationClique = SigFormat{ + "application/clique", + 0x01, + } + DataPlain = SigFormat{ + "data/plain", + 0x45, + } + DataStructured = SigFormat{ + "data/structured", + 0x46, + } +) + // MetadataFromContext extracts Metadata from a given context.Context func MetadataFromContext(ctx context.Context) Metadata { m := Metadata{"NA", "NA", "NA", "", ""} // batman @@ -523,7 +547,7 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth // where the V value will be 27 or 28 for legacy reasons. func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { - var req, err = api.determineSignatureFormat(contentType, data) + var req, err = api.DetermineSignatureFormat(contentType, data) if err != nil { return nil, err } @@ -555,41 +579,16 @@ func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr com return signature, nil } -// EcRecover 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 -// the address of: -// hash = keccak256("\x19Ethereum Signed Message:\n"${message length}${message}) -// addr = ecrecover(hash, signature) -// -// Note, the signature must conform to the secp256k1 curve R, S and V values, where -// the V value must be be 27 or 28 for legacy reasons. -// -// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover -func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data, sig hexutil.Bytes) (common.Address, error) { - if len(sig) != 65 { - return common.Address{}, fmt.Errorf("signature must be 65 bytes long") - } - if sig[64] != 27 && sig[64] != 28 { - return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") - } - sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 - hash, _ := SignDataPlain(data) - rpk, err := crypto.SigToPub(hash, sig) - if err != nil { - return common.Address{}, err - } - return crypto.PubkeyToAddress(*rpk), nil -} - // Determines which signature method should be used based upon the mime type -func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil.Bytes) (*SignDataRequest, error) { +func (api *SignerAPI) DetermineSignatureFormat(contentType string, data hexutil.Bytes) (*SignDataRequest, error) { var req *SignDataRequest mediaType, _, err := mime.ParseMediaType(contentType) if err != nil { return nil, err } switch mediaType { - case "application/clique": + case ApplicationClique.Mime: + // Clique is the Ethereum PoA standard header := &types.Header{} if err := rlp.DecodeBytes(data, header); err != nil { return nil, err @@ -600,40 +599,26 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil. } msg := fmt.Sprintf("Clique block %d [0x%x]", header.Number, header.Hash()) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - case "application/validator": - // Sign calculates an Ethereum ECDSA signature for: - // keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) - - // In the cases where it matter ensure that the charset is handled. The charset - // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType - // charset, ok := params["charset"] - // As it is now, we accept any charset and just treat it as 'raw'. + case ApplicationValidator.Mime: + // Data with an intended validator - sighash, msg := DataWithValidatorHash(data) + sighash, msg := SignDataWithValidator(data) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - case "data/structured": - // EIP712 typed data + case DataStructured.Mime: + // Typed data according to EIP712 - // Sign calculates an Ethereum ECDSA signature for: - // keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) - - // In the cases where it matter ensure that the charset is handled. The charset - // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType - // charset, ok := params["charset"] - // As it is now, we accept any charset and just treat it as 'raw'. - - sighash, msg := DataStructuredHash(data) + sighash, msg := SignDataStructured(data) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - case "data/plain": + case DataPlain.Mime: // Sign calculates an Ethereum ECDSA signature for: - // keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) + // keccack256("\x19${byte version}Ethereum Signed Message:\n" + len(message) + message)) - // In the cases where it matter ensure that the charset is handled. The charset + // In the cases where it matters ensure that the charset is handled. The charset // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType // charset, ok := params["charset"] // As it is now, we accept any charset and just treat it as 'raw'. - sighash, msg := DataPlainHash(data) + sighash, msg := SignDataPlain(data) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} default: return nil, fmt.Errorf("content type '%s' not implemented for signing", contentType) @@ -676,32 +661,72 @@ func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { return hash.Bytes(), nil } -// DataWithValidatorHash signs the given message according to EIP191. -// -// https://github.com/ethereum/EIPs/issues/712 -func DataWithValidatorHash(data []byte) ([]byte, string) { - return nil, "" +// DataWithValidatorHash signs the given message which can be further recovered +// with the given validator. +func SignDataWithValidator(data []byte) ([]byte, string) { + msg := "TODO" + return crypto.Keccak256([]byte(msg)), msg } // DataStructuredHash signs the given message according to EIP712. // // https://github.com/ethereum/EIPs/issues/712 -func DataStructuredHash(data []byte) ([]byte, string) { - return nil, "" +func SignDataStructured(data []byte) ([]byte, string) { + msg := "TODO" + return crypto.Keccak256([]byte(msg)), msg } -// DataPlainHash is a helper function that calculates a hash for the given message that can be +// SignDataPlain is a helper function that calculates a hash for the given message that can be // safely used to calculate a signature from. // // The hash is calculated as -// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). +// keccak256("\x19${byte version}Ethereum Signed Message:\n"${message length}${message}). // // This gives context to the signed message and prevents signing of transactions. -func DataPlainHash(data []byte) ([]byte, string) { - msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) +func SignDataPlain(data []byte) ([]byte, string) { + msg := fmt.Sprintf("\x19\\x%xEthereum Signed Message:\n%d%s", DataPlain.ByteVersion, len(data), data) return crypto.Keccak256([]byte(msg)), msg } +// Determines the content type and then recovers the address associated with the given sig +func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data, sig hexutil.Bytes) (common.Address, error) { + fmt.Println("Effing Muffins") + mediaType, _, err := mime.ParseMediaType(contentType) + if err != nil { + return common.Address{}, err + } + switch mediaType { + case DataPlain.Mime: + // 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 + // the address of: + // hash = keccak256("\x19${byte version}Ethereum Signed Message:\n"${message length}${message}) + // addr = ecrecover(hash, signature) + // + // Note, the signature must conform to the secp256k1 curve R, S and V values, where + // the V value must be be 27 or 28 for legacy reasons. + // + // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover + + if len(sig) != 65 { + return common.Address{}, fmt.Errorf("signature must be 65 bytes long") + } + if sig[64] != 27 && sig[64] != 28 { + return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") + } + sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 + hash, _ := SignDataPlain(data) + rpk, err := crypto.SigToPub(hash, sig) + if err != nil { + return common.Address{}, err + } + return crypto.PubkeyToAddress(*rpk), nil + default: + return common.Address{}, fmt.Errorf("content type '%s' not implemented for ecRecover", contentType) + } +} + // Export returns encrypted private key associated with the given address in web3 keystore format. func (api *SignerAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) { res, err := api.UI.ApproveExport(&ExportRequest{Address: addr, Meta: MetadataFromContext(ctx)}) From af391da9df57fff321f04e18271a44db45a05681 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sat, 29 Sep 2018 17:16:34 +0300 Subject: [PATCH 03/84] Added ecRecover method in the clef api --- signer/core/api.go | 3 ++- signer/core/auditlog.go | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/signer/core/api.go b/signer/core/api.go index 900392e822d5..a5c5900a1005 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -52,6 +52,8 @@ type ExternalAPI interface { SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) // SignData - request to sign the given data (plus prefix) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) + // EcRecover - recover public key from given message and signature + EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) // Export - request to export an account Export(ctx context.Context, addr common.Address) (json.RawMessage, error) // Import - request to import an account @@ -690,7 +692,6 @@ func SignDataPlain(data []byte) ([]byte, string) { // Determines the content type and then recovers the address associated with the given sig func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data, sig hexutil.Bytes) (common.Address, error) { - fmt.Println("Effing Muffins") mediaType, _, err := mime.ParseMediaType(contentType) if err != nil { return common.Address{}, err diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go index a0fb1b076706..ac16b144d0e6 100644 --- a/signer/core/auditlog.go +++ b/signer/core/auditlog.go @@ -71,6 +71,14 @@ func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr com return b, e } +func (l *AuditLogger) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { + l.log.Info("EcRecover", "type", "request", "metadata", MetadataFromContext(ctx).String(), + "data", common.Bytes2Hex(data), "sig", common.Bytes2Hex(sig), "content-type", contentType) + b, e := l.api.EcRecover(ctx, contentType, data, sig) + l.log.Info("EcRecover", "type", "response", "address", b.String(), "error", e) + return b, e +} + func (l *AuditLogger) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) { l.log.Info("Export", "type", "request", "metadata", MetadataFromContext(ctx).String(), "addr", addr.Hex()) From 35dcd8c30646cdaaefc65ec98a677c7e7e6e3a3a Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sat, 29 Sep 2018 17:52:05 +0300 Subject: [PATCH 04/84] Updated the extapi changelog and addded indications in the README --- cmd/clef/README.md | 13 ++++++++----- cmd/clef/extapi_changelog.md | 11 +++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/cmd/clef/README.md b/cmd/clef/README.md index c9461be10194..8fcaae7cb590 100644 --- a/cmd/clef/README.md +++ b/cmd/clef/README.md @@ -347,12 +347,13 @@ Bash example: ``` -### account_sign +### account_signData #### Sign data Signs a chunk of data and returns the calculated signature. #### Arguments + - content type [string]: type of data to sign - account [address]: account to sign with - data [data]: data to sign @@ -364,8 +365,9 @@ Bash example: { "id": 3, "jsonrpc": "2.0", - "method": "account_sign", + "method": "account_signData", "params": [ + "data/plain", "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db", "0xaabbccdd" ] @@ -383,10 +385,11 @@ Response ### account_ecRecover -#### Recover address +#### Sign data Derive the address from the account that was used to sign data from the data and signature. - + #### Arguments + - content type [string]: type of signed data - data [data]: data that was signed - signature [data]: the signature to verify @@ -400,6 +403,7 @@ Response "jsonrpc": "2.0", "method": "account_ecRecover", "params": [ + "data/plain", "0xaabbccdd", "0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c" ] @@ -413,7 +417,6 @@ Response "jsonrpc": "2.0", "result": "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db" } - ``` ### account_import diff --git a/cmd/clef/extapi_changelog.md b/cmd/clef/extapi_changelog.md index 6c2c3e819430..693e6b17599a 100644 --- a/cmd/clef/extapi_changelog.md +++ b/cmd/clef/extapi_changelog.md @@ -1,10 +1,21 @@ ### Changelog for external API +#### 5.0.0 + +* The external `account_EcRecover`-method was added again. +* The external method `accounts_Sign(address, data)` was replaced with `accounts_signData(contentType, address, data)`. +The addition of `contentType` makes it possible to use the method for different types of objects, such as: + * signing data with an intended validator (not yet implemented) + * signing clique headers, + * signing plain personal messages, + * signing structured data adhering to [EIP-712](https://eips.ethereum.org/EIPS/eip-712) (not yet implemented) + #### 4.0.0 * The external `account_Ecrecover`-method was removed. * The external `account_Import`-method was removed. + #### 3.0.0 * The external `account_List`-method was changed to not expose `url`, which contained info about the local filesystem. It now returns only a list of addresses. From 5142521b84846082a2c2312245dac84d287fdb54 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sat, 29 Sep 2018 17:58:01 +0300 Subject: [PATCH 05/84] Changed the version of the external API --- cmd/clef/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 898a63829699..2d0805648b88 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -50,7 +50,7 @@ import ( ) // ExternalAPIVersion -- see extapi_changelog.md -const ExternalAPIVersion = "4.0.0" +const ExternalAPIVersion = "5.0.0" // InternalAPIVersion -- see intapi_changelog.md const InternalAPIVersion = "3.0.0" From a16c250409eae4b6cc14c31b00fdc3a39acd54bf Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Wed, 3 Oct 2018 00:16:27 +0100 Subject: [PATCH 06/84] Added tests for 0x45 --- signer/core/api.go | 8 ++++---- signer/core/api_test.go | 41 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/signer/core/api.go b/signer/core/api.go index a5c5900a1005..68fe9b7837a4 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -111,8 +111,8 @@ type Metadata struct { } type SigFormat struct { - Mime string - ByteVersion byte + Mime string + ByteVersion byte } var ( @@ -663,14 +663,14 @@ func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { return hash.Bytes(), nil } -// DataWithValidatorHash signs the given message which can be further recovered +// SignDataWithValidator signs the given message which can be further recovered // with the given validator. func SignDataWithValidator(data []byte) ([]byte, string) { msg := "TODO" return crypto.Keccak256([]byte(msg)), msg } -// DataStructuredHash signs the given message according to EIP712. +// SignDataStructured signs the given message according to EIP712. // // https://github.com/ethereum/EIPs/issues/712 func SignDataStructured(data []byte) ([]byte, string) { diff --git a/signer/core/api_test.go b/signer/core/api_test.go index abe67c9f4d18..bf49dfaa946c 100644 --- a/signer/core/api_test.go +++ b/signer/core/api_test.go @@ -245,7 +245,21 @@ func TestNewAcc(t *testing.T) { } } -func TestSignData(t *testing.T) { +func signApplicationValidator(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) { api, control := setup(t) //Create two accounts createAccount(control, api, t) @@ -259,7 +273,7 @@ func TestSignData(t *testing.T) { control <- "Y" control <- "wrongpassword" - h, err := api.SignData(context.Background(), "text/plain", a, []byte("EHLO world")) + h, err := api.SignData(context.Background(), DataPlain.Mime, a, []byte("EHLO world")) if h != nil { t.Errorf("Expected nil-data, got %x", h) } @@ -267,7 +281,7 @@ func TestSignData(t *testing.T) { t.Errorf("Expected ErrLocked! %v", err) } control <- "No way" - h, err = api.SignData(context.Background(), "text/plain", a, []byte("EHLO world")) + h, err = api.SignData(context.Background(), DataPlain.Mime, a, []byte("EHLO world")) if h != nil { t.Errorf("Expected nil-data, got %x", h) } @@ -276,7 +290,7 @@ func TestSignData(t *testing.T) { } control <- "Y" control <- "a_long_password" - h, err = api.SignData(context.Background(), "text/plain", a, []byte("EHLO world")) + h, err = api.SignData(context.Background(), DataPlain.Mime, a, []byte("EHLO world")) if err != nil { t.Fatal(err) } @@ -284,6 +298,25 @@ func TestSignData(t *testing.T) { t.Errorf("Expected 65 byte signature (got %d bytes)", len(h)) } } + +func signDataStructured(t *testing.T) { + // TODO +} + +func TestSignData(t *testing.T) { + // application/validator or `0x00` + signApplicationValidator(t) + + // application/clique or `0x01` + signApplicationClique(t) + + // data/plain or `0x45` + signDataPlain(t) + + // data/structured `0x46` + signDataStructured(t) +} + func mkTestTx(from common.MixedcaseAddress) SendTxArgs { to := common.NewMixedcaseAddress(common.HexToAddress("0x1337")) gas := hexutil.Uint64(21000) From e440c25a2bb08279ecd9e1ccc87da535ef7287f4 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Wed, 10 Oct 2018 14:53:53 -0700 Subject: [PATCH 07/84] Implementing UnmarshalJSON() for TypedData --- cmd/clef/audit.log | 11 ++++ cmd/clef/main.go | 24 +++---- interfaces.go | 5 ++ signer/core/api.go | 2 + signer/core/api_layout.md | 134 ++++++++++++++++++++++++++++++++++++++ signer/core/apiv2.go | 57 ++++++++++++++++ signer/core/auditlog.go | 9 ++- 7 files changed, 230 insertions(+), 12 deletions(-) create mode 100644 cmd/clef/audit.log create mode 100644 signer/core/api_layout.md create mode 100644 signer/core/apiv2.go diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log new file mode 100644 index 000000000000..66302cac9786 --- /dev/null +++ b/cmd/clef/audit.log @@ -0,0 +1,11 @@ +t=2018-10-10T14:45:26-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-10T14:45:37-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59942\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data={PrimaryType:0x4d61696c} +t=2018-10-10T14:45:37-0700 lvl=info msg=SignStructuredData api=signer type=response data= error=nil +t=2018-10-10T14:45:55-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-10T14:45:55-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59956\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data={PrimaryType:0x4d61696c} +t=2018-10-10T14:46:18-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-10T14:46:50-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59967\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data={PrimaryType:0x4d61696c} +t=2018-10-10T14:51:32-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-10T14:51:59-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60000\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data="{Hash:[226 140 80 200 149 243 254 73 125 193 200 45 83 189 176 154 4 204 167 86 220 130 208 11 170 238 116 129 150 85 62 148]}" +t=2018-10-10T14:52:51-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-10T14:52:56-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60017\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data="{Hash:[226 140 80 200 149 243 254 73 125 193 200 45 83 189 176 154 4 204 167 86 220 130 208 11 170 238 116 129 150 85 62 148]}" diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 2d0805648b88..6c1290b9cde5 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -327,9 +327,10 @@ func initialize(c *cli.Context) error { // If using the stdioui, we can't do the 'confirm'-flow fmt.Fprintf(logOutput, legalWarning) } else { - if !confirm(legalWarning) { - return fmt.Errorf("aborted by user") - } + // Temporarily disabled while in development + //if !confirm(legalWarning) { + // return fmt.Errorf("aborted by user") + //} } log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int(logLevelFlag.Name)), log.StreamHandler(logOutput, log.TerminalFormat(true)))) @@ -552,14 +553,15 @@ 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 { - resp, err := ui.OnInputRequired(core.UserInputRequest{ - Title: "Master Password", - Prompt: "Please enter the password to decrypt the master seed", - IsPassword: true}) - if err != nil { - return nil, err - } - password = resp.Text + // Temporarily disabled while in development + //resp, err := ui.OnInputRequired(core.UserInputRequest{ + // Title: "Master Password", + // Prompt: "Please enter the password to decrypt the master seed", + // IsPassword: true}) + //if err != nil { + // return nil, err + //} + //password = resp.Text } else { password = getPassPhrase("Decrypt master seed of clef", false) } diff --git a/interfaces.go b/interfaces.go index 1ff31f96b6a6..840e916aaf72 100644 --- a/interfaces.go +++ b/interfaces.go @@ -209,3 +209,8 @@ type GasEstimator interface { type PendingStateEventer interface { SubscribePendingTransactions(ctx context.Context, ch chan<- *types.Transaction) (Subscription, error) } + +// TypedData is the standard for EIP-712 signed data. +type TypedData struct { + Hash *common.Hash `json:"hash"` +} diff --git a/signer/core/api.go b/signer/core/api.go index 68fe9b7837a4..6171331313fd 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -52,6 +52,8 @@ type ExternalAPI interface { SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) // SignData - request to sign the given data (plus prefix) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) + // SignStructuredData - request to sign the given structured data (plus prefix) + SignStructuredData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) // EcRecover - recover public key from given message and signature EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) // Export - request to export an account diff --git a/signer/core/api_layout.md b/signer/core/api_layout.md new file mode 100644 index 000000000000..bcb8c4cc5bb2 --- /dev/null +++ b/signer/core/api_layout.md @@ -0,0 +1,134 @@ +# Specs +`encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01" β€– domainSeparator β€– hashStruct(message)` +- data adheres to π•Š, a structure defined in the rigorous eip-712 +- `\x01` is needed to comply with EIP-191 +- `domainSeparator` and `hashStruct` are defined below + +## A) domainSeparator +`domainSeparator = hashStruct(eip712Domain)` +
+
+Struct named `EIP712Domain` with one or more of the below fields: + +- `string name` +- `string version` +- `uint256 chainId`, as per EIP-155 +- `address verifyingContract` +- `bytes32 salt` + +## B) hashStruct +`hashStruct(s : π•Š) = keccak256(typeHash β€– encodeData(s))` +
+`typeHash = keccak256(encodeType(typeOf(s)))` + +### i) encodeType +- `name β€– "(" β€– member₁ β€– "," β€– memberβ‚‚ β€– "," β€– … β€– memberβ‚™ ")"` +- each member is written as `type β€– " " β€– name` +- encodings cascade down and are sorted by name + +### ii) encodeData +- `enc(value₁) β€– enc(valueβ‚‚) β€– … β€– enc(valueβ‚™)` +- each encoded member is 32-byte long + + #### a) atomic + + - `boolean` => `uint256` + - `address` => `uint160` + - `uint` => sign-extended `uint256` in big endian order + - `bytes1:31` => `bytes32` + + #### b) dynamic + + - `bytes` => `keccak256(bytes)` + - `string` => `keccak256(string)` + + #### c) referenced + + - `array` => `keccak256(encodeData(array))` + - `struct` => `rec(keccak256(hashStruct(struct)))` + +## C) Example +### Query +```json +{ + "jsonrpc": "2.0", + "method": "account_signStructuredData", + "params": [ + "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + { + "types": { + "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" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": 1, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } + } + ], + "id": 1 +} +``` + +### Response +```json +{ + "id":1, + "jsonrpc": "2.0", + "result": "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c" +} +``` \ No newline at end of file diff --git a/signer/core/apiv2.go b/signer/core/apiv2.go new file mode 100644 index 000000000000..0ef25cb8d204 --- /dev/null +++ b/signer/core/apiv2.go @@ -0,0 +1,57 @@ +package core + +import ( + "context" + "encoding/json" + "fmt" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +//type TypedData struct { + //Types map[string] interface{} `json:"types"` + //PrimaryType *big.Int `json:"primaryType"` + //Domain EIP712Domain `json:"domain"` + //Message map[string] interface{} `json:"message"` +//} + +//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"` +//} + +// TypedData represents a request to create a new filter. +// Same as ethereum.FilterQuery but with UnmarshalJSON() method. +type TypedData ethereum.TypedData + +// Typed data according to EIP712 +// +// If the format "\x19\x46" β€– domainSeparator β€– hashStruct(message)` is not respected, +// an error is returned +func (api *SignerAPI) SignStructuredData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) { + fmt.Println("data", data) + fmt.Println("data.Hash", data.Hash) + return common.Hex2Bytes("0xdeadbeef"), nil +} + +// UnmarshalJSON sets *args fields with given data. +func (args *TypedData) UnmarshalJSON(data []byte) error { + type input struct { + Hash *common.Hash `json:"hash"` + } + + var raw input + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + if raw.Hash != nil { + args.Hash = raw.Hash + } + + return nil +} \ No newline at end of file diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go index ac16b144d0e6..ed14d5cc649e 100644 --- a/signer/core/auditlog.go +++ b/signer/core/auditlog.go @@ -18,7 +18,6 @@ package core import ( "context" - "encoding/json" "github.com/ethereum/go-ethereum/accounts" @@ -71,6 +70,14 @@ func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr com return b, e } +func (l *AuditLogger) SignStructuredData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) { + l.log.Info("SignStructuredData", "type", "request", "metadata", MetadataFromContext(ctx).String(), + "addr", addr.String(), "data", data) + b, e := l.api.SignStructuredData(ctx, addr, TypedData{}) + l.log.Info("SignStructuredData", "type", "response", "data", common.Bytes2Hex(b), "error", e) + return b, e +} + func (l *AuditLogger) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { l.log.Info("EcRecover", "type", "request", "metadata", MetadataFromContext(ctx).String(), "data", common.Bytes2Hex(data), "sig", common.Bytes2Hex(sig), "content-type", contentType) From 35cb892b686caa31bd8199555553bac3f0d185e6 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Thu, 11 Oct 2018 13:05:54 -0700 Subject: [PATCH 08/84] Working on TypedData --- cmd/clef/audit.log | 11 -------- signer/core/api.go | 41 ++++++++++++++++++++++------- signer/core/{apiv2.go => apiv2.goz} | 2 +- signer/core/auditlog.go | 6 ++--- 4 files changed, 35 insertions(+), 25 deletions(-) delete mode 100644 cmd/clef/audit.log rename signer/core/{apiv2.go => apiv2.goz} (91%) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log deleted file mode 100644 index 66302cac9786..000000000000 --- a/cmd/clef/audit.log +++ /dev/null @@ -1,11 +0,0 @@ -t=2018-10-10T14:45:26-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-10T14:45:37-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59942\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data={PrimaryType:0x4d61696c} -t=2018-10-10T14:45:37-0700 lvl=info msg=SignStructuredData api=signer type=response data= error=nil -t=2018-10-10T14:45:55-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-10T14:45:55-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59956\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data={PrimaryType:0x4d61696c} -t=2018-10-10T14:46:18-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-10T14:46:50-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59967\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data={PrimaryType:0x4d61696c} -t=2018-10-10T14:51:32-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-10T14:51:59-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60000\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data="{Hash:[226 140 80 200 149 243 254 73 125 193 200 45 83 189 176 154 4 204 167 86 220 130 208 11 170 238 116 129 150 85 62 148]}" -t=2018-10-10T14:52:51-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-10T14:52:56-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60017\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data="{Hash:[226 140 80 200 149 243 254 73 125 193 200 45 83 189 176 154 4 204 167 86 220 130 208 11 170 238 116 129 150 85 62 148]}" diff --git a/signer/core/api.go b/signer/core/api.go index 6171331313fd..603fcc2e91e4 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -21,6 +21,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto/sha3" "io/ioutil" @@ -53,7 +54,7 @@ type ExternalAPI interface { // SignData - request to sign the given data (plus prefix) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) // SignStructuredData - request to sign the given structured data (plus prefix) - SignStructuredData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) + SignStructuredData(ctx context.Context, data TypedData) (hexutil.Bytes, error) // EcRecover - recover public key from given message and signature EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) // Export - request to export an account @@ -608,11 +609,6 @@ func (api *SignerAPI) DetermineSignatureFormat(contentType string, data hexutil. sighash, msg := SignDataWithValidator(data) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - case DataStructured.Mime: - // Typed data according to EIP712 - - sighash, msg := SignDataStructured(data) - req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} case DataPlain.Mime: // Sign calculates an Ethereum ECDSA signature for: // keccack256("\x19${byte version}Ethereum Signed Message:\n" + len(message) + message)) @@ -672,12 +668,37 @@ func SignDataWithValidator(data []byte) ([]byte, string) { return crypto.Keccak256([]byte(msg)), msg } -// SignDataStructured signs the given message according to EIP712. +// TypedData represents a request to create a new filter. +type TypedData ethereum.TypedData + +// SignStructuredData signs the given message according to EIP712. // // https://github.com/ethereum/EIPs/issues/712 -func SignDataStructured(data []byte) ([]byte, string) { - msg := "TODO" - return crypto.Keccak256([]byte(msg)), msg +// +// If the format "\x19\x46" β€– domainSeparator β€– hashStruct(message)` is not respected, +// an error is returned +func (api *SignerAPI) SignStructuredData(ctx context.Context, data TypedData) (hexutil.Bytes, error) { + fmt.Println("data", data) + fmt.Println("data.Hash", data.Hash) + return common.Hex2Bytes("0xdeadbeef"), nil +} + +// UnmarshalJSON sets *args fields with given data. +func (args *TypedData) UnmarshalJSON(data []byte) error { + type input struct { + Hash *common.Hash `json:"hash"` + } + + var raw input + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + if raw.Hash != nil { + args.Hash = raw.Hash + } + + return nil } // SignDataPlain is a helper function that calculates a hash for the given message that can be diff --git a/signer/core/apiv2.go b/signer/core/apiv2.goz similarity index 91% rename from signer/core/apiv2.go rename to signer/core/apiv2.goz index 0ef25cb8d204..87716a8736df 100644 --- a/signer/core/apiv2.go +++ b/signer/core/apiv2.goz @@ -32,7 +32,7 @@ type TypedData ethereum.TypedData // // If the format "\x19\x46" β€– domainSeparator β€– hashStruct(message)` is not respected, // an error is returned -func (api *SignerAPI) SignStructuredData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) { +func (api *SignerAPI) SignStructuredData(ctx context.Context, data TypedData) (hexutil.Bytes, error) { fmt.Println("data", data) fmt.Println("data.Hash", data.Hash) return common.Hex2Bytes("0xdeadbeef"), nil diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go index ed14d5cc649e..93b165568f5b 100644 --- a/signer/core/auditlog.go +++ b/signer/core/auditlog.go @@ -70,10 +70,10 @@ func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr com return b, e } -func (l *AuditLogger) SignStructuredData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) { +func (l *AuditLogger) SignStructuredData(ctx context.Context, data TypedData) (hexutil.Bytes, error) { l.log.Info("SignStructuredData", "type", "request", "metadata", MetadataFromContext(ctx).String(), - "addr", addr.String(), "data", data) - b, e := l.api.SignStructuredData(ctx, addr, TypedData{}) + "addr", "data", data) + b, e := l.api.SignStructuredData(ctx, TypedData{}) l.log.Info("SignStructuredData", "type", "response", "data", common.Bytes2Hex(b), "error", e) return b, e } From ae5583a93cfd19829d9163c7e6f0a526e53494aa Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sat, 13 Oct 2018 05:04:06 -0700 Subject: [PATCH 09/84] Solved the auditlog issue --- cmd/clef/audit.log | 1 + interfaces.go | 7 +---- signer/core/api.go | 34 ------------------------ signer/core/apiv2.go | 56 ++++++++++++++++++++++++++++++++++++++++ signer/core/apiv2.goz | 57 ----------------------------------------- signer/core/auditlog.go | 2 +- 6 files changed, 59 insertions(+), 98 deletions(-) create mode 100644 cmd/clef/audit.log create mode 100644 signer/core/apiv2.go delete mode 100644 signer/core/apiv2.goz diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log new file mode 100644 index 000000000000..c5b542d9b0ba --- /dev/null +++ b/cmd/clef/audit.log @@ -0,0 +1 @@ +t=2018-10-13T05:03:16-0700 lvl=info msg=Configured api=signer audit log=audit.log diff --git a/interfaces.go b/interfaces.go index 840e916aaf72..a7de78249164 100644 --- a/interfaces.go +++ b/interfaces.go @@ -208,9 +208,4 @@ type GasEstimator interface { // pending state. type PendingStateEventer interface { SubscribePendingTransactions(ctx context.Context, ch chan<- *types.Transaction) (Subscription, error) -} - -// TypedData is the standard for EIP-712 signed data. -type TypedData struct { - Hash *common.Hash `json:"hash"` -} +} \ No newline at end of file diff --git a/signer/core/api.go b/signer/core/api.go index 603fcc2e91e4..3453dbacc803 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -21,7 +21,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto/sha3" "io/ioutil" @@ -668,39 +667,6 @@ func SignDataWithValidator(data []byte) ([]byte, string) { return crypto.Keccak256([]byte(msg)), msg } -// TypedData represents a request to create a new filter. -type TypedData ethereum.TypedData - -// SignStructuredData signs the given message according to EIP712. -// -// https://github.com/ethereum/EIPs/issues/712 -// -// If the format "\x19\x46" β€– domainSeparator β€– hashStruct(message)` is not respected, -// an error is returned -func (api *SignerAPI) SignStructuredData(ctx context.Context, data TypedData) (hexutil.Bytes, error) { - fmt.Println("data", data) - fmt.Println("data.Hash", data.Hash) - return common.Hex2Bytes("0xdeadbeef"), nil -} - -// UnmarshalJSON sets *args fields with given data. -func (args *TypedData) UnmarshalJSON(data []byte) error { - type input struct { - Hash *common.Hash `json:"hash"` - } - - var raw input - if err := json.Unmarshal(data, &raw); err != nil { - return err - } - - if raw.Hash != nil { - args.Hash = raw.Hash - } - - return nil -} - // SignDataPlain is a helper function that calculates a hash for the given message that can be // safely used to calculate a signature from. // diff --git a/signer/core/apiv2.go b/signer/core/apiv2.go new file mode 100644 index 000000000000..4f3e737300c3 --- /dev/null +++ b/signer/core/apiv2.go @@ -0,0 +1,56 @@ +package core + +import ( + "context" + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "math/big" +) + +type TypedData struct { + Types map[string] interface{} `json:"types"` + PrimaryType string `json:"primaryType"` + Domain EIP712Domain `json:"domain"` + Message map[string] interface{} `json:"message"` +} + +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"` +} + +// Typed data according to EIP712 +// +// If the format "\x19\x46" β€– domainSeparator β€– hashStruct(message)` is not respected, +// an error is returned +func (api *SignerAPI) SignStructuredData(ctx context.Context, data TypedData) (hexutil.Bytes, error) { + fmt.Println("data", data) + fmt.Println("data.PrimaryType", data.PrimaryType) + return common.Hex2Bytes("0xdeadbeef"), nil +} + +// TypedData represents a request to create a new filter. +// Same as ethereum.FilterQuery but with UnmarshalJSON() method. +//type TypedData ethereum.TypedData + +// UnmarshalJSON sets *args fields with given data. +//func (args *TypedData) UnmarshalJSON(data []byte) error { +// type input struct { +// Hash *common.Hash `json:"hash"` +// } +// +// var raw input +// if err := json.Unmarshal(data, &raw); err != nil { +// return err +// } +// +// if raw.Hash != nil { +// args.Hash = raw.Hash +// } +// +// return nil +//} \ No newline at end of file diff --git a/signer/core/apiv2.goz b/signer/core/apiv2.goz deleted file mode 100644 index 87716a8736df..000000000000 --- a/signer/core/apiv2.goz +++ /dev/null @@ -1,57 +0,0 @@ -package core - -import ( - "context" - "encoding/json" - "fmt" - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" -) - -//type TypedData struct { - //Types map[string] interface{} `json:"types"` - //PrimaryType *big.Int `json:"primaryType"` - //Domain EIP712Domain `json:"domain"` - //Message map[string] interface{} `json:"message"` -//} - -//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"` -//} - -// TypedData represents a request to create a new filter. -// Same as ethereum.FilterQuery but with UnmarshalJSON() method. -type TypedData ethereum.TypedData - -// Typed data according to EIP712 -// -// If the format "\x19\x46" β€– domainSeparator β€– hashStruct(message)` is not respected, -// an error is returned -func (api *SignerAPI) SignStructuredData(ctx context.Context, data TypedData) (hexutil.Bytes, error) { - fmt.Println("data", data) - fmt.Println("data.Hash", data.Hash) - return common.Hex2Bytes("0xdeadbeef"), nil -} - -// UnmarshalJSON sets *args fields with given data. -func (args *TypedData) UnmarshalJSON(data []byte) error { - type input struct { - Hash *common.Hash `json:"hash"` - } - - var raw input - if err := json.Unmarshal(data, &raw); err != nil { - return err - } - - if raw.Hash != nil { - args.Hash = raw.Hash - } - - return nil -} \ No newline at end of file diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go index 93b165568f5b..e80635ed023e 100644 --- a/signer/core/auditlog.go +++ b/signer/core/auditlog.go @@ -73,7 +73,7 @@ func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr com func (l *AuditLogger) SignStructuredData(ctx context.Context, data TypedData) (hexutil.Bytes, error) { l.log.Info("SignStructuredData", "type", "request", "metadata", MetadataFromContext(ctx).String(), "addr", "data", data) - b, e := l.api.SignStructuredData(ctx, TypedData{}) + b, e := l.api.SignStructuredData(ctx, data) l.log.Info("SignStructuredData", "type", "response", "data", common.Bytes2Hex(b), "error", e) return b, e } From 86dbe8aa3e397f15704a996560e4a2325f9b8d1a Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sun, 14 Oct 2018 19:57:28 +0100 Subject: [PATCH 10/84] Changed method to signTypedData --- cmd/clef/audit.log | 15 +++++ signer/core/api.go | 122 +++++++++++++++++++++----------------- signer/core/api_layout.md | 4 +- signer/core/apiv2.go | 32 ++-------- signer/core/auditlog.go | 10 ++-- 5 files changed, 96 insertions(+), 87 deletions(-) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log index c5b542d9b0ba..d339f7e75c37 100644 --- a/cmd/clef/audit.log +++ b/cmd/clef/audit.log @@ -1 +1,16 @@ t=2018-10-13T05:03:16-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-13T05:04:21-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-13T05:04:35-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61564\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr=data LOG15_ERROR= LOG15_ERROR="Normalized odd number of arguments by adding nil" +t=2018-10-13T05:04:35-0700 lvl=info msg=SignStructuredData api=signer type=response data= error=nil +t=2018-10-13T05:07:31-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-13T05:35:59-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-13T05:37:45-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-13T05:42:26-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-13T05:43:13-0700 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61669\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[type:string name:version] 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:{neg:false abs:[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-13T05:43:13-0700 lvl=info msg=SignTypedData api=signer type=response data= error=nil +t=2018-10-13T05:43:50-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-13T05:44:06-0700 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61680\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Mail:[map[name:from type:Person] map[type:Person name:to] 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]] Person:[map[type:string name:name] map[name:wallet type:address]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:{neg:false abs:[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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-13T05:44:06-0700 lvl=info msg=SignTypedData api=signer type=response data= error=nil +t=2018-10-13T05:46:53-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-13T05:46:58-0700 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61694\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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-13T05:46:58-0700 lvl=info msg=SignTypedData api=signer type=response data= error=nil diff --git a/signer/core/api.go b/signer/core/api.go index 3453dbacc803..8384790de6f0 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -53,7 +53,7 @@ type ExternalAPI interface { // SignData - request to sign the given data (plus prefix) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) // SignStructuredData - request to sign the given structured data (plus prefix) - SignStructuredData(ctx context.Context, 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(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) // Export - request to export an account @@ -118,21 +118,21 @@ type SigFormat struct { } var ( - ApplicationValidator = SigFormat{ - "application/validator", + TextPlain = SigFormat{ + "text/plain", 0x00, } - ApplicationClique = SigFormat{ - "application/clique", + TextValidator = SigFormat{ + "text/validator", 0x01, } - DataPlain = SigFormat{ - "data/plain", + DataTyped = SigFormat{ + "data/typed", 0x45, } - DataStructured = SigFormat{ - "data/structured", - 0x46, + ApplicationClique = SigFormat{ + "application/clique", + 0x90, } ) @@ -551,7 +551,7 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth // where the V value will be 27 or 28 for legacy reasons. func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { - var req, err = api.DetermineSignatureFormat(contentType, data) + var req, err = api.determineSignatureFormat(contentType, data) if err != nil { return nil, err } @@ -584,40 +584,56 @@ func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr com } // Determines which signature method should be used based upon the mime type -func (api *SignerAPI) DetermineSignatureFormat(contentType string, data hexutil.Bytes) (*SignDataRequest, error) { +func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil.Bytes) (*SignDataRequest, error) { var req *SignDataRequest mediaType, _, err := mime.ParseMediaType(contentType) if err != nil { return nil, err } switch mediaType { - case ApplicationClique.Mime: - // Clique is the Ethereum PoA standard - header := &types.Header{} - if err := rlp.DecodeBytes(data, header); err != nil { - return nil, err - } - sighash, err := SignCliqueHeader(header) - if err != nil { - return nil, err - } - msg := fmt.Sprintf("Clique block %d [0x%x]", header.Number, header.Hash()) - req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - case ApplicationValidator.Mime: + case TextValidator.Mime: // Data with an intended validator - sighash, msg := SignDataWithValidator(data) + sighash, msg := signTextWithValidator(data) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - case DataPlain.Mime: + case TextPlain.Mime: // Sign calculates an Ethereum ECDSA signature for: - // keccack256("\x19${byte version}Ethereum Signed Message:\n" + len(message) + message)) + // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") // In the cases where it matters ensure that the charset is handled. The charset // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType // charset, ok := params["charset"] // As it is now, we accept any charset and just treat it as 'raw'. - sighash, msg := SignDataPlain(data) + sighash, msg := signTextPlain(data) + req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + //case DataTyped.Mime: + // // Typed data according to EIP712: + // // + // // hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}") + // fmt.Println("Did we get here, chief? #1") + // typedData := TypedData{} + // if err := rlp.DecodeBytes(data, typedData); err != nil { + // return nil, err + // } + // fmt.Println("Did we get here, chief? #2") + // sighash, err := signTypedData(context.Background(), typedData) + // if err != nil { + // return nil, err + // } + // msg := fmt.Sprintf("Typed data domain %s", typedData.Domain) + // req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + case ApplicationClique.Mime: + // Clique is the Ethereum PoA standard + header := &types.Header{} + if err := rlp.DecodeBytes(data, header); err != nil { + return nil, err + } + sighash, err := signCliqueHeader(header) + if err != nil { + return nil, err + } + msg := fmt.Sprintf("Clique block %d [0x%x]", header.Number, header.Hash()) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} default: return nil, fmt.Errorf("content type '%s' not implemented for signing", contentType) @@ -626,6 +642,25 @@ func (api *SignerAPI) DetermineSignatureFormat(contentType string, data hexutil. } +// signTextPlain is a helper function that calculates a hash for the given message that can be +// safely used to calculate a signature from. +// +// The hash is calculated as +// keccak256("\x19${byteVersion}Ethereum Signed Message:\n"${message length}${message}). +// +// This gives context to the signed message and prevents signing of transactions. +func signTextPlain(data []byte) ([]byte, string) { + msg := fmt.Sprintf("\x19\\x%xEthereum Signed Message:\n%d%s", TextPlain.ByteVersion, len(data), data) + return crypto.Keccak256([]byte(msg)), msg +} + +// signTextWithValidator signs the given message which can be further recovered +// with the given validator. +func signTextWithValidator(data []byte) ([]byte, string) { + msg := "TODO" + return crypto.Keccak256([]byte(msg)), msg +} + // SignCliqueHeader returns the hash which is used as input for the proof-of-authority // signing. It is the hash of the entire header apart from the 65 byte signature // contained at the end of the extra data. @@ -633,7 +668,7 @@ func (api *SignerAPI) DetermineSignatureFormat(contentType string, data hexutil. // The method requires the extra data to be at least 65 bytes -- the original implementation // in clique.go panics if this is the case, thus it's been reimplemented here to avoid the panic // and simply return an error instead -func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { +func signCliqueHeader(header *types.Header) (hexutil.Bytes, error) { hash := common.Hash{} if len(header.Extra) < 65 { return hash.Bytes(), fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) @@ -660,38 +695,19 @@ func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { return hash.Bytes(), nil } -// SignDataWithValidator signs the given message which can be further recovered -// with the given validator. -func SignDataWithValidator(data []byte) ([]byte, string) { - msg := "TODO" - return crypto.Keccak256([]byte(msg)), msg -} - -// SignDataPlain is a helper function that calculates a hash for the given message that can be -// safely used to calculate a signature from. -// -// The hash is calculated as -// keccak256("\x19${byte version}Ethereum Signed Message:\n"${message length}${message}). -// -// This gives context to the signed message and prevents signing of transactions. -func SignDataPlain(data []byte) ([]byte, string) { - msg := fmt.Sprintf("\x19\\x%xEthereum Signed Message:\n%d%s", DataPlain.ByteVersion, len(data), data) - return crypto.Keccak256([]byte(msg)), msg -} - // Determines the content type and then recovers the address associated with the given sig -func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data, sig hexutil.Bytes) (common.Address, error) { +func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { mediaType, _, err := mime.ParseMediaType(contentType) if err != nil { return common.Address{}, err } switch mediaType { - case DataPlain.Mime: + case TextPlain.Mime: // 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 // the address of: - // hash = keccak256("\x19${byte version}Ethereum Signed Message:\n"${message length}${message}) + // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") // addr = ecrecover(hash, signature) // // Note, the signature must conform to the secp256k1 curve R, S and V values, where @@ -706,7 +722,7 @@ func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data, s return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") } sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 - hash, _ := SignDataPlain(data) + hash, _ := signTextPlain(data) rpk, err := crypto.SigToPub(hash, sig) if err != nil { return common.Address{}, err diff --git a/signer/core/api_layout.md b/signer/core/api_layout.md index bcb8c4cc5bb2..289f711eb9f1 100644 --- a/signer/core/api_layout.md +++ b/signer/core/api_layout.md @@ -1,7 +1,7 @@ # Specs -`encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01" β€– domainSeparator β€– hashStruct(message)` +`encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x45" β€– domainSeparator β€– hashStruct(message)` - data adheres to π•Š, a structure defined in the rigorous eip-712 -- `\x01` is needed to comply with EIP-191 +- `\x45` is needed to comply with EIP-191 - `domainSeparator` and `hashStruct` are defined below ## A) domainSeparator diff --git a/signer/core/apiv2.go b/signer/core/apiv2.go index 4f3e737300c3..3485bcdf6436 100644 --- a/signer/core/apiv2.go +++ b/signer/core/apiv2.go @@ -25,32 +25,10 @@ type EIP712Domain struct { // Typed data according to EIP712 // -// If the format "\x19\x46" β€– domainSeparator β€– hashStruct(message)` is not respected, -// an error is returned -func (api *SignerAPI) SignStructuredData(ctx context.Context, data TypedData) (hexutil.Bytes, error) { - fmt.Println("data", data) - fmt.Println("data.PrimaryType", data.PrimaryType) +// hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}") +func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) { + fmt.Println("addr", addr) + //fmt.Println("data", data) + fmt.Println("data.Domain", data.Domain) return common.Hex2Bytes("0xdeadbeef"), nil } - -// TypedData represents a request to create a new filter. -// Same as ethereum.FilterQuery but with UnmarshalJSON() method. -//type TypedData ethereum.TypedData - -// UnmarshalJSON sets *args fields with given data. -//func (args *TypedData) UnmarshalJSON(data []byte) error { -// type input struct { -// Hash *common.Hash `json:"hash"` -// } -// -// var raw input -// if err := json.Unmarshal(data, &raw); err != nil { -// return err -// } -// -// if raw.Hash != nil { -// args.Hash = raw.Hash -// } -// -// return nil -//} \ No newline at end of file diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go index e80635ed023e..c89c50f79ea5 100644 --- a/signer/core/auditlog.go +++ b/signer/core/auditlog.go @@ -70,11 +70,11 @@ func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr com return b, e } -func (l *AuditLogger) SignStructuredData(ctx context.Context, data TypedData) (hexutil.Bytes, error) { - l.log.Info("SignStructuredData", "type", "request", "metadata", MetadataFromContext(ctx).String(), - "addr", "data", data) - b, e := l.api.SignStructuredData(ctx, data) - l.log.Info("SignStructuredData", "type", "response", "data", common.Bytes2Hex(b), "error", e) +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(), + "addr", addr.String(), "data", data) + b, e := l.api.SignTypedData(ctx, addr, data) + l.log.Info("SignTypedData", "type", "response", "data", common.Bytes2Hex(b), "error", e) return b, e } From d6a424206646b4c67a77d47d3afdc2e8c6793ea8 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Mon, 15 Oct 2018 03:36:07 +0100 Subject: [PATCH 11/84] Changed mimes and implemented the 'encodeType' function for EIP-712 --- cmd/clef/audit.log | 48 +++++++ cmd/clef/extapi_changelog.md | 6 +- cmd/clef/main.go | 34 ++++- interfaces.go | 2 +- signer/core/api.go | 32 ++--- signer/core/api_layout.md | 10 +- signer/core/apiv2.go | 251 +++++++++++++++++++++++++++++++++-- 7 files changed, 337 insertions(+), 46 deletions(-) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log index d339f7e75c37..9b0ef72b850f 100644 --- a/cmd/clef/audit.log +++ b/cmd/clef/audit.log @@ -14,3 +14,51 @@ t=2018-10-13T05:44:06-0700 lvl=info msg=SignTypedData api=signer type=response d t=2018-10-13T05:46:53-0700 lvl=info msg=Configured api=signer audit log=audit.log t=2018-10-13T05:46:58-0700 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61694\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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-13T05:46:58-0700 lvl=info msg=SignTypedData api=signer type=response data= error=nil +t=2018-10-14T21:00:04+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T21:00:06+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56799\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-14T21:00:06+0100 lvl=info msg=SignTypedData api=signer type=response data= error="Couldn't decode types of EIP712Domain" +t=2018-10-14T21:00:50+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T21:01:02+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56810\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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:{neg:false abs:[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-14T21:01:02+0100 lvl=info msg=SignTypedData api=signer type=response data= error="Couldn't decode types of EIP712Domain" +t=2018-10-14T21:01:39+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T21:01:53+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56823\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-14T21:01:53+0100 lvl=info msg=SignTypedData api=signer type=response data= error="Couldn't decode types of EIP712Domain" +t=2018-10-14T21:11:30+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T21:11:33+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56899\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] map[name:verifyingContract type:address]] Person:[map[type:string name:name] 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:{neg:false abs:[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-14T21:11:33+0100 lvl=info msg=SignTypedData api=signer type=response data= error="Couldn't decode types of EIP712Domain" +t=2018-10-14T21:15:28+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T21:15:34+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56968\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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-14T21:15:34+0100 lvl=info msg=SignTypedData api=signer type=response data= error="Couldn't decode types of EIP712Domain" +t=2018-10-14T21:17:53+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T21:17:57+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56991\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:Person name:to] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:{neg:false abs:[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-14T21:17:57+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-14T21:18:32+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T21:18:36+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57006\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:Person name:from] map[name:to type:Person] map[type:string name:contents]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:{neg:false abs:[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-14T21:18:36+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-14T21:18:59+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T21:19:00+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57017\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] EIP712Domain:[map[name:name type:string] map[type:string name:version] map[name:chainId type:uint256] map[name:verifyingContract type:address]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:{neg:false abs:[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-14T21:19:00+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-14T21:30:19+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T21:30:29+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57188\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-14T21:30:29+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-14T21:51:40+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T21:51:42+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57392\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] EIP712Domain:[map[name:name type:string] map[type:string name:version] 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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-14T21:51:42+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-14T22:16:07+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T22:16:14+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57602\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]]}" +t=2018-10-14T22:16:14+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-14T22:18:31+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T22:18:35+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57653\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-14T22:18:35+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-14T22:20:47+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T22:20:50+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57669\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]] EIP712Domain:[map[name:name type:string] map[type:string name:version] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet 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-14T22:20:50+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-15T03:11:09+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-15T03:11:17+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50605\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-15T03:11:17+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-15T03:12:01+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-15T03:12:04+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50618\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-15T03:12:04+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-15T03:14:48+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-15T03:15:25+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50635\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-15T03:15:25+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil diff --git a/cmd/clef/extapi_changelog.md b/cmd/clef/extapi_changelog.md index 693e6b17599a..a9ab42e5b521 100644 --- a/cmd/clef/extapi_changelog.md +++ b/cmd/clef/extapi_changelog.md @@ -2,13 +2,13 @@ #### 5.0.0 -* The external `account_EcRecover`-method was added again. -* The external method `accounts_Sign(address, data)` was replaced with `accounts_signData(contentType, address, data)`. +* The external `account_EcRecover`-method was reimplemented. +* The external method `account_sign(address, data)` was replaced with `account_signData(contentType, address, data)`. The addition of `contentType` makes it possible to use the method for different types of objects, such as: * signing data with an intended validator (not yet implemented) * signing clique headers, * signing plain personal messages, - * signing structured data adhering to [EIP-712](https://eips.ethereum.org/EIPS/eip-712) (not yet implemented) +* The external method `account_signTypedData` [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md) and makes it possible to sign typed data. #### 4.0.0 diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 6c1290b9cde5..dca06e9a7ee3 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -26,8 +26,11 @@ import ( "encoding/hex" "encoding/json" "fmt" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" "io" "io/ioutil" + "math/big" "os" "os/signal" "os/user" @@ -631,10 +634,40 @@ func testExternalUI(api *core.SignerAPI) { } var err error + cliqueHeader := types.Header{ + common.HexToHash("0000H45H"), + common.HexToHash("0000H45H"), + common.HexToAddress("0000H45H"), + common.HexToHash("0000H00H"), + common.HexToHash("0000H45H"), + common.HexToHash("0000H45H"), + types.Bloom{}, + big.NewInt(1337), + big.NewInt(1337), + 1338, + 1338, + big.NewInt(1338), + []byte("Extra data Extra data Extra data Extra data Extra data Extra data Extra data Extra data"), + common.HexToHash("0x0000H45H"), + types.BlockNonce{}, + } + cliqueRlp, err := rlp.EncodeToBytes(cliqueHeader) + if err != nil { + utils.Fatalf("Should not error: %v", err) + } + addr, err := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899") + if err != nil { + utils.Fatalf("Should not error: %v", err) + } + _, err = api.SignData(ctx, "application/clique", *addr, cliqueRlp) + checkErr("SignData", err) + _, err = api.SignTransaction(ctx, core.SendTxArgs{From: common.MixedcaseAddress{}}, nil) checkErr("SignTransaction", err) _, err = api.SignData(ctx, "text/plain", common.MixedcaseAddress{}, common.Hex2Bytes("01020304")) checkErr("SignData", err) + _, err = api.SignTypedData(ctx, common.MixedcaseAddress{}, core.TypedData{}) + checkErr("SignTypedData", err) _, err = api.List(ctx) checkErr("List", err) _, err = api.New(ctx) @@ -654,7 +687,6 @@ func testExternalUI(api *core.SignerAPI) { } else { log.Info("No errors") } - } // getPassPhrase retrieves the password associated with clef, either fetched diff --git a/interfaces.go b/interfaces.go index a7de78249164..1ff31f96b6a6 100644 --- a/interfaces.go +++ b/interfaces.go @@ -208,4 +208,4 @@ type GasEstimator interface { // pending state. type PendingStateEventer interface { SubscribePendingTransactions(ctx context.Context, ch chan<- *types.Transaction) (Subscription, error) -} \ No newline at end of file +} diff --git a/signer/core/api.go b/signer/core/api.go index 8384790de6f0..691372f2499f 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -118,21 +118,21 @@ type SigFormat struct { } var ( - TextPlain = SigFormat{ - "text/plain", - 0x00, - } TextValidator = SigFormat{ "text/validator", - 0x01, + 0x00, } DataTyped = SigFormat{ "data/typed", - 0x45, + 0x01, } ApplicationClique = SigFormat{ "application/clique", - 0x90, + 0x02, + } + TextPlain = SigFormat{ + "text/plain", + 0x45, } ) @@ -607,22 +607,6 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil. sighash, msg := signTextPlain(data) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - //case DataTyped.Mime: - // // Typed data according to EIP712: - // // - // // hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}") - // fmt.Println("Did we get here, chief? #1") - // typedData := TypedData{} - // if err := rlp.DecodeBytes(data, typedData); err != nil { - // return nil, err - // } - // fmt.Println("Did we get here, chief? #2") - // sighash, err := signTypedData(context.Background(), typedData) - // if err != nil { - // return nil, err - // } - // msg := fmt.Sprintf("Typed data domain %s", typedData.Domain) - // req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} case ApplicationClique.Mime: // Clique is the Ethereum PoA standard header := &types.Header{} @@ -661,7 +645,7 @@ func signTextWithValidator(data []byte) ([]byte, string) { return crypto.Keccak256([]byte(msg)), msg } -// SignCliqueHeader returns the hash which is used as input for the proof-of-authority +// signCliqueHeader returns the hash which is used as input for the proof-of-authority // signing. It is the hash of the entire header apart from the 65 byte signature // contained at the end of the extra data. // diff --git a/signer/core/api_layout.md b/signer/core/api_layout.md index 289f711eb9f1..1a251fe9eef6 100644 --- a/signer/core/api_layout.md +++ b/signer/core/api_layout.md @@ -1,7 +1,7 @@ # Specs -`encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x45" β€– domainSeparator β€– hashStruct(message)` +`encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01" β€– domainSeparator β€– hashStruct(message)` - data adheres to π•Š, a structure defined in the rigorous eip-712 -- `\x45` is needed to comply with EIP-191 +- `\x00` is needed to comply with EIP-191 - `domainSeparator` and `hashStruct` are defined below ## A) domainSeparator @@ -26,6 +26,8 @@ Struct named `EIP712Domain` with one or more of the below fields: - each member is written as `type β€– " " β€– name` - encodings cascade down and are sorted by name +Example: `Mail(Person from,Person to,string contents)Person(string name,address wallet)` + ### ii) encodeData - `enc(value₁) β€– enc(valueβ‚‚) β€– … β€– enc(valueβ‚™)` - each encoded member is 32-byte long @@ -52,7 +54,7 @@ Struct named `EIP712Domain` with one or more of the below fields: ```json { "jsonrpc": "2.0", - "method": "account_signStructuredData", + "method": "account_signTypedData", "params": [ "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", { @@ -131,4 +133,4 @@ Struct named `EIP712Domain` with one or more of the below fields: "jsonrpc": "2.0", "result": "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c" } -``` \ No newline at end of file +``` diff --git a/signer/core/apiv2.go b/signer/core/apiv2.go index 3485bcdf6436..8c87ebd8448a 100644 --- a/signer/core/apiv2.go +++ b/signer/core/apiv2.go @@ -1,34 +1,259 @@ package core import ( + "bytes" "context" + "encoding/hex" "fmt" "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 map[string] interface{} `json:"types"` - PrimaryType string `json:"primaryType"` - Domain EIP712Domain `json:"domain"` - Message map[string] interface{} `json:"message"` + Types EIP712Types `json:"types"` + PrimaryType string `json:"primaryType"` + Domain EIP712Domain `json:"domain"` + Message EIP712Message `json:"message"` +} + +type EIP712Types map[string][]map[string]string + +type EIP712TypePriority struct { + Type string + Value uint } 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 common.Address `json:"verifyingContract"` + Salt hexutil.Bytes `json:"salt"` } +type EIP712Message map[string]interface{} + // Typed data according to EIP712 // // hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}") func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) { - fmt.Println("addr", addr) - //fmt.Println("data", data) - fmt.Println("data.Domain", data.Domain) - return common.Hex2Bytes("0xdeadbeef"), nil + if err := data.Domain.IsValid(); err != nil { + return nil, err + } + if data.PrimaryType == "" { + return nil, fmt.Errorf("primary type undefined") + } + + domainTypes := EIP712Types{ + "EIP712Domain": data.Types["EIP712Domain"], + } + domainSeparator, err := hashStruct(domainTypes, data.Domain.Values(), "") + 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 + } + + fmt.Println("domainSeparator", domainSeparator.String()) + fmt.Println("typedDataHash", typedDataHash.String()) + return common.FromHex("0xdeadbeef"), nil +} + +// `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") + } + } + + typeEncoding, err := encodeType(types, primaryType) + if err != nil { + return common.Hash{}, err + } + typeHash := hex.EncodeToString(crypto.Keccak256([]byte(typeEncoding))) + + dataEncoding, err := encodeData(message) + if err != nil { + return common.Hash{}, err + } + dataHash := hex.EncodeToString(crypto.Keccak256([]byte(dataEncoding))) + + var buffer bytes.Buffer + buffer.WriteString(typeHash) + buffer.WriteString(dataHash) + hash := common.BytesToHash(crypto.Keccak256(buffer.Bytes())) + + return hash, nil +} + +// encodeType transforms the given types into an encoding of the form +// `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) { + 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 { + for _, elem := range arr { + if elem == val { + return true + } + } + return false + } + + for typeKey, typeArr := range types { + var typeValArr []string + + for _, typeObj := range typeArr { + typeVal := typeObj["type"] + if typeKey == typeVal { + return "", fmt.Errorf("type %s cannot reference itself", typeVal) + } + + firstChar := []rune(typeVal)[0] + if unicode.IsUpper(firstChar) { + if types[typeVal] != nil { + if !visited(typeValArr, typeVal) { + typeValArr = append(typeValArr, typeVal) + update(typeKey, typeVal) + } + } else { + return "", fmt.Errorf("referenced type %s is undefined", typeVal) + } + } else { + if !types.IsStandardType(typeVal) { + if types[typeVal] != nil { + return "", fmt.Errorf("Custom type %s must be capitalized", typeVal) + } else { + return "", fmt.Errorf("Unknown type %s", typeVal) + } + } + } + } + + typeValArr = []string{} + } + + sortedPriorities := types.SortByPriorityAndName(priorities) + var buffer bytes.Buffer + for _, priority := range sortedPriorities { + typeKey := priority.Type + typeArr := types[typeKey] + + buffer.WriteString(typeKey) + buffer.WriteString("(") + + for _, typeObj := range typeArr { + buffer.WriteString(typeObj["type"]) + buffer.WriteString(" ") + buffer.WriteString(typeObj["name"]) + buffer.WriteString(",") + } + + buffer.Truncate(buffer.Len() - 1) + buffer.WriteString(")") + } + + return buffer.String(), nil +} + +func encodeData(values EIP712Message) (string, error) { + return "", nil +} + +// Checks if the given type is a standard type accepted by EIP-712 +func (types *EIP712Types) IsStandardType(typeStr string) bool { + standardTypes := []string{ + "array", + "address", + "boolean", + "bytes", + "string", + "struct", + "uint", + } + for _, val := range standardTypes { + if strings.HasPrefix(typeStr, val) { + return true + } + } + return false +} + +// 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 { + 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 + }) + + for _, priority := range priorities { + fmt.Printf("%s, Value %d\n", priority.Type, priority.Value) + } + fmt.Printf("\n") + + return priorities +} + +// Check 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") + } + + if domain.Name == "" && domain.Version == "" && len(domain.VerifyingContract) == 0 && len(domain.Salt) == 0 { + return fmt.Errorf("domain undefined") + } + + return nil +} + +// Helper function to return the values of a domain in the form of a golang map +func (domain *EIP712Domain) Values() map[string]interface{} { + return map[string]interface{}{ + "name": domain.Name, + "version": domain.Version, + "chainId": domain.Name, + "verifyingContract": domain.VerifyingContract, + "salt": domain.Salt, + } } From a7003c7032b2271cb4094d8c6e67f42e4a6a3bbd Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Thu, 18 Oct 2018 09:16:43 +0100 Subject: [PATCH 12/84] 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} | 173 +++++++++++++++-------- 2 files changed, 129 insertions(+), 76 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 bf49dfaa946c..f1695feec935 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` - signApplicationClique(t) + // data/structured `0x01` + signTypedData(t) - // data/plain or `0x45` - signDataPlain(t) + // application/clique or `0x02` + signApplicationClique(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 8c87ebd8448a..79e49989ac42 100644 --- a/signer/core/apiv2.go +++ b/signer/core/signed_data.go @@ -4,30 +4,39 @@ import ( "bytes" "context" "encoding/hex" + "errors" "fmt" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" "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" ) 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, From 35b83c1b9f463441f77babb9516789d11f7edb54 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sun, 21 Oct 2018 13:58:00 +0100 Subject: [PATCH 13/84] Drafted recursive encodeData --- cmd/clef/audit.log | 84 ++++++ signer/core/api.go | 179 ------------ signer/core/api_layout.md | 24 +- signer/core/signed_data.go | 571 +++++++++++++++++++++++++++++-------- 4 files changed, 562 insertions(+), 296 deletions(-) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log index 9b0ef72b850f..667d0443b4f6 100644 --- a/cmd/clef/audit.log +++ b/cmd/clef/audit.log @@ -62,3 +62,87 @@ t=2018-10-15T03:12:04+0100 lvl=info msg=SignTypedData api=signer type=response d t=2018-10-15T03:14:48+0100 lvl=info msg=Configured api=signer audit log=audit.log t=2018-10-15T03:15:25+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50635\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" t=2018-10-15T03:15:25+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-20T15:17:32+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:17:53+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:22:24+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:24:48+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:24:54+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56168\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[] PrimaryType: Domain:{Name: Version: ChainId: VerifyingContract:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Salt:0x} Message:map[]}" +t=2018-10-20T15:24:54+0100 lvl=info msg=SignTypedData api=signer type=response data= error="primary type undefined" +t=2018-10-20T15:25:28+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:27:12+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:27:59+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:28:02+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56262\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[] PrimaryType: Domain:{Name: Version: ChainId: VerifyingContract:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Salt:0x} Message:map[]}" +t=2018-10-20T15:28:02+0100 lvl=info msg=SignTypedData api=signer type=response data= error="primary type undefined" +t=2018-10-20T15:28:15+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56262\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[] PrimaryType: Domain:{Name: Version: ChainId: VerifyingContract:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Salt:0x} Message:map[]}" +t=2018-10-20T15:28:15+0100 lvl=info msg=SignTypedData api=signer type=response data= error="primary type undefined" +t=2018-10-20T15:29:47+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:29:52+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56281\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[] PrimaryType: Domain:{Name: Version: ChainId: VerifyingContract:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Salt:0x} Message:map[]}" +t=2018-10-20T15:32:20+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:32:46+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56456\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:Person name:from] map[type:Person name:to] 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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-20T15:34:35+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:35:24+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56476\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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:[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-20T15:37:05+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:37:21+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56561\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:Person name:to] 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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T15:38:27+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:38:36+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56583\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[type:string name:version] 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[to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" +t=2018-10-20T15:40:49+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:40:54+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56604\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T15:41:20+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:41:38+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56681\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[type:string name:name] map[type:string name:version] 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:[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-20T15:49:10+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:49:20+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56804\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:string name:contents]]] 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-20T15:51:01+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:51:31+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56859\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Mail:[map[type:Person name:from] 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]] Person:[map[name:name type:string] map[name:wallet 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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-20T15:51:59+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:52:23+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56905\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T16:05:44+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:05:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58137\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-20T16:06:22+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:06:25+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58154\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[type:string name:name] map[type:string name:version] 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[type:Person name:to] 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-20T16:06:39+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:06:43+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58213\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[type:string name:name] map[name:version type:string] map[name:chainId type:uint256] map[type:address name:verifyingContract]] 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:[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-20T16:06:55+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:07:53+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58301\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] Person:[map[type:string name:name] map[name:wallet 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[contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]]}" +t=2018-10-20T16:10:03+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:10:05+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58446\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:string name:contents]] 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:[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-20T16:10:39+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:10:43+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58497\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T16:12:31+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:12:37+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58538\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[type:string name:version] 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:[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-20T16:12:55+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58538\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T16:14:08+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:14:11+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58563\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[type:string name:version] map[name:chainId type:uint256] map[type:address name:verifyingContract]] 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:[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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice]]}" +t=2018-10-20T16:17:07+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:17:13+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58619\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T16:18:51+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:19:01+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:20:03+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58718\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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:[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-20T16:24:34+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:25:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58919\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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-20T16:30:18+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:30:40+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58983\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T16:30:59+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:31:01+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59036\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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-20T16:31:36+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:32:25+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59121\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice]]}" +t=2018-10-20T19:01:23+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:01:51+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59614\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] Person:[map[name:name type:string] map[name:wallet 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-20T19:02:26+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:02:41+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59722\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T19:06:00+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:07:47+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60455\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T19:08:16+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60455\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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[contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]]}" +t=2018-10-20T19:09:46+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60587\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T19:11:12+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:11:21+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60710\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" +t=2018-10-20T19:11:35+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60710\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] 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-20T19:11:51+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:11:53+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60768\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] 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-20T19:12:11+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:12:30+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60809\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" +t=2018-10-20T19:13:53+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:14:04+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60898\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T19:14:32+0100 lvl=info msg=SignTypedData api=signer type=response data=686365d6fa3d2a98d7c67101a8acf295c644a8de80b8ecc89cff276355897f6d error=nil +t=2018-10-20T19:36:35+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:36:40+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61953\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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-20T19:36:40+0100 lvl=info msg=SignTypedData api=signer type=response data=7951623617a41fec181fafc3555c0a494d01dce0408b6596964f5f50acba239e error=nil diff --git a/signer/core/api.go b/signer/core/api.go index 691372f2499f..136b3682a493 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -21,11 +21,8 @@ import ( "encoding/json" "errors" "fmt" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto/sha3" "io/ioutil" "math/big" - "mime" "reflect" "github.com/ethereum/go-ethereum/accounts" @@ -33,7 +30,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/usbwallet" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" @@ -542,181 +538,6 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth } -// SignData signs the hash of the provided data, but does so differently -// depending on the content-type specified. -// -// Depending on the content-type, different types of validations will occur. -// -// Note, the produced signature conforms to the secp256k1 curve R, S and V values, -// where the V value will be 27 or 28 for legacy reasons. -func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { - - var req, err = api.determineSignatureFormat(contentType, data) - if err != nil { - return nil, err - } - req.Address = addr - req.Meta = MetadataFromContext(ctx) - - // We make the request prior to looking up if we actually have the account, to prevent - // account-enumeration via the API - res, err := api.UI.ApproveSignData(req) - if err != nil { - return nil, err - } - if !res.Approved { - return nil, ErrRequestDenied - } - // Look up the wallet containing the requested signer - account := accounts.Account{Address: addr.Address()} - wallet, err := api.am.Find(account) - if err != nil { - return nil, err - } - // Sign the data with the wallet - signature, err := wallet.SignHashWithPassphrase(account, res.Password, req.Hash) - if err != nil { - api.UI.ShowError(err.Error()) - return nil, err - } - signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper - return signature, nil -} - -// Determines which signature method should be used based upon the mime type -func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil.Bytes) (*SignDataRequest, error) { - var req *SignDataRequest - mediaType, _, err := mime.ParseMediaType(contentType) - if err != nil { - return nil, err - } - switch mediaType { - case TextValidator.Mime: - // Data with an intended validator - - sighash, msg := signTextWithValidator(data) - req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - case TextPlain.Mime: - // Sign calculates an Ethereum ECDSA signature for: - // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") - - // In the cases where it matters ensure that the charset is handled. The charset - // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType - // charset, ok := params["charset"] - // As it is now, we accept any charset and just treat it as 'raw'. - - sighash, msg := signTextPlain(data) - req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - case ApplicationClique.Mime: - // Clique is the Ethereum PoA standard - header := &types.Header{} - if err := rlp.DecodeBytes(data, header); err != nil { - return nil, err - } - sighash, err := signCliqueHeader(header) - if err != nil { - return nil, err - } - msg := fmt.Sprintf("Clique block %d [0x%x]", header.Number, header.Hash()) - req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - default: - return nil, fmt.Errorf("content type '%s' not implemented for signing", contentType) - } - return req, nil - -} - -// signTextPlain is a helper function that calculates a hash for the given message that can be -// safely used to calculate a signature from. -// -// The hash is calculated as -// keccak256("\x19${byteVersion}Ethereum Signed Message:\n"${message length}${message}). -// -// This gives context to the signed message and prevents signing of transactions. -func signTextPlain(data []byte) ([]byte, string) { - msg := fmt.Sprintf("\x19\\x%xEthereum Signed Message:\n%d%s", TextPlain.ByteVersion, len(data), data) - return crypto.Keccak256([]byte(msg)), msg -} - -// signTextWithValidator signs the given message which can be further recovered -// with the given validator. -func signTextWithValidator(data []byte) ([]byte, string) { - msg := "TODO" - return crypto.Keccak256([]byte(msg)), msg -} - -// signCliqueHeader returns the hash which is used as input for the proof-of-authority -// signing. It is the hash of the entire header apart from the 65 byte signature -// contained at the end of the extra data. -// -// The method requires the extra data to be at least 65 bytes -- the original implementation -// in clique.go panics if this is the case, thus it's been reimplemented here to avoid the panic -// and simply return an error instead -func signCliqueHeader(header *types.Header) (hexutil.Bytes, error) { - hash := common.Hash{} - if len(header.Extra) < 65 { - return hash.Bytes(), fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) - } - hasher := sha3.NewKeccak256() - rlp.Encode(hasher, []interface{}{ - header.ParentHash, - header.UncleHash, - header.Coinbase, - header.Root, - header.TxHash, - header.ReceiptHash, - header.Bloom, - header.Difficulty, - header.Number, - header.GasLimit, - header.GasUsed, - header.Time, - header.Extra[:len(header.Extra)-65], - header.MixDigest, - header.Nonce, - }) - hasher.Sum(hash[:0]) - return hash.Bytes(), nil -} - -// Determines the content type and then recovers the address associated with the given sig -func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { - mediaType, _, err := mime.ParseMediaType(contentType) - if err != nil { - return common.Address{}, err - } - switch mediaType { - case TextPlain.Mime: - // 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 - // the address of: - // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") - // addr = ecrecover(hash, signature) - // - // Note, the signature must conform to the secp256k1 curve R, S and V values, where - // the V value must be be 27 or 28 for legacy reasons. - // - // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover - - if len(sig) != 65 { - return common.Address{}, fmt.Errorf("signature must be 65 bytes long") - } - if sig[64] != 27 && sig[64] != 28 { - return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") - } - sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 - hash, _ := signTextPlain(data) - rpk, err := crypto.SigToPub(hash, sig) - if err != nil { - return common.Address{}, err - } - return crypto.PubkeyToAddress(*rpk), nil - default: - return common.Address{}, fmt.Errorf("content type '%s' not implemented for ecRecover", contentType) - } -} - // Export returns encrypted private key associated with the given address in web3 keystore format. func (api *SignerAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) { res, err := api.UI.ApproveExport(&ExportRequest{Address: addr, Meta: MetadataFromContext(ctx)}) diff --git a/signer/core/api_layout.md b/signer/core/api_layout.md index 1a251fe9eef6..3aae11fd07b7 100644 --- a/signer/core/api_layout.md +++ b/signer/core/api_layout.md @@ -1,7 +1,7 @@ # Specs -`encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01" β€– domainSeparator β€– hashStruct(message)` +`encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01EthereumSignedMessage\n" β€– domainSeparator β€– hashStruct(message)` - data adheres to π•Š, a structure defined in the rigorous eip-712 -- `\x00` is needed to comply with EIP-191 +- `\x01` is needed to comply with EIP-191 - `domainSeparator` and `hashStruct` are defined below ## A) domainSeparator @@ -34,9 +34,9 @@ Example: `Mail(Person from,Person to,string contents)Person(string name,address #### a) atomic - - `boolean` => `uint256` + - `bool` => `uint256` - `address` => `uint160` - - `uint` => sign-extended `uint256` in big endian order + - `int8:int256` and `uint8:uint256` => sign-extended `uint256` in big endian order - `bytes1:31` => `bytes32` #### b) dynamic @@ -49,7 +49,21 @@ Example: `Mail(Person from,Person to,string contents)Person(string name,address - `array` => `keccak256(encodeData(array))` - `struct` => `rec(keccak256(hashStruct(struct)))` -## C) Example +## C) Algo +- hashStruct + - encodeType + - encodeData + - if primitive + - encode + - else + - if array + - encodeData + - else if struct + - hashStruct + - else + - break + +## D) Example ### Query ```json { diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 79e49989ac42..eef2c89b3c96 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -4,14 +4,18 @@ import ( "bytes" "context" "encoding/hex" + "encoding/json" "errors" "fmt" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto/sha3" + "github.com/ethereum/go-ethereum/rlp" "math/big" - "math/rand" + "mime" "reflect" "sort" "strings" - "time" "unicode" "github.com/PaulRBerg/basics/helpers" @@ -22,14 +26,16 @@ import ( ) type TypedData struct { - Types map[string]EIP712Type `json:"types"` - PrimaryType string `json:"primaryType"` - Domain EIP712Domain `json:"domain"` - Message EIP712Message `json:"message"` + Types EIP712Types `json:"types"` + PrimaryType string `json:"primaryType"` + Domain EIP712Domain `json:"domain"` + Message EIP712Data `json:"message"` } type EIP712Type []map[string]string +type EIP712Types map[string]EIP712Type + type EIP712TypePriority struct { Type string Value uint @@ -38,79 +44,255 @@ 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 common.Address `json:"verifyingContract"` + Salt hexutil.Bytes `json:"salt"` } -type EIP712Message map[string]interface{} +const ( + TypeArray = "array" + TypeAddress = "address" + TypeBool = "bool" + TypeBytes = "bytes" + TypeInt = "int" + TypeString = "string" +) -// 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 { +// Sign receives a request and produces a signature + +// Note, the produced signature conforms to the secp256k1 curve R, S and V values, +// where the V value will be 27 or 28 for legacy reasons. +func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, req *SignDataRequest) ([]byte, error) { + req.Address = addr + req.Meta = MetadataFromContext(ctx) + + // We make the request prior to looking up if we actually have the account, to prevent + // account-enumeration via the API + res, err := api.UI.ApproveSignData(req) + if err != nil { + return nil, err + } + if !res.Approved { + return nil, ErrRequestDenied + } + // Look up the wallet containing the requested signer + account := accounts.Account{Address: addr.Address()} + wallet, err := api.am.Find(account) + if err != nil { + return nil, err + } + // Sign the data with the wallet + signature, err := wallet.SignHashWithPassphrase(account, res.Password, req.Hash) + if err != nil { + return nil, err + } + signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper + return signature, nil +} + +// SignData signs the hash of the provided data, but does so differently +// depending on the content-type specified. +// +// Different types of validation occur. +func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { + var req, err = api.determineSignatureFormat(contentType, data) + if err != nil { + return nil, err + } + + signature, err := api.Sign(ctx, addr, req) + if err != nil { + api.UI.ShowError(err.Error()) + return nil, err + } + + return signature, nil +} + +// Determines which signature method should be used based upon the mime type +func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil.Bytes) (*SignDataRequest, error) { + var req *SignDataRequest + mediaType, _, err := mime.ParseMediaType(contentType) + if err != nil { return nil, err } - if data.PrimaryType == "" { - return nil, errors.New("primary type undefined") + + switch mediaType { + case TextValidator.Mime: + // Data with an intended validator + sighash, msg := signTextWithValidator(data) + req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + break + case TextPlain.Mime: + // Sign calculates an Ethereum ECDSA signature for: + // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") + + // In the cases where it matters ensure that the charset is handled. The charset + // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType + // charset, ok := params["charset"] + // As it is now, we accept any charset and just treat it as 'raw'. + sighash, msg := signTextPlain(data) + req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + break + case ApplicationClique.Mime: + // Clique is the Ethereum PoA standard + header := &types.Header{} + if err := rlp.DecodeBytes(data, header); err != nil { + return nil, err + } + sighash, err := signCliqueHeader(header) + if err != nil { + return nil, err + } + msg := fmt.Sprintf("Clique block %d [0x%x]", header.Number, header.Hash()) + req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + break + default: + return nil, fmt.Errorf("content type '%s' not implemented for signing", contentType) } + return req, nil + +} - domainTypes := map[string]EIP712Type{ - "EIP712Domain": data.Types["EIP712Domain"], +// signTextPlain is a helper function that calculates a hash for the given message that can be +// safely used to calculate a signature from. +// +// The hash is calculated as +// keccak256("\x19${byteVersion}Ethereum Signed Message:\n"${message length}${message}). +// +// This gives context to the signed message and prevents signing of transactions. +func signTextPlain(data []byte) ([]byte, string) { + msg := fmt.Sprintf("\x19\\x%xEthereum Signed Message:\n%d%s", TextPlain.ByteVersion, len(data), data) + return crypto.Keccak256([]byte(msg)), msg +} + +// signTextWithValidator signs the given message which can be further recovered +// with the given validator. +func signTextWithValidator(data []byte) ([]byte, string) { + msg := "TODO" + return crypto.Keccak256([]byte(msg)), msg +} + +// signCliqueHeader returns the hash which is used as input for the proof-of-authority +// signing. It is the hash of the entire header apart from the 65 byte signature +// contained at the end of the extra data. +// +// The method requires the extra data to be at least 65 bytes -- the original implementation +// in clique.go panics if this is the case, thus it's been reimplemented here to avoid the panic +// and simply return an error instead +func signCliqueHeader(header *types.Header) (hexutil.Bytes, error) { + hash := common.Hash{} + if len(header.Extra) < 65 { + return hash.Bytes(), fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) } - domainSeparator := hashStruct(domainTypes, data.PrimaryType, data.Domain.Values(), 0) + hasher := sha3.NewKeccak256() + rlp.Encode(hasher, []interface{}{ + header.ParentHash, + header.UncleHash, + header.Coinbase, + header.Root, + header.TxHash, + header.ReceiptHash, + header.Bloom, + header.Difficulty, + header.Number, + header.GasLimit, + header.GasUsed, + header.Time, + header.Extra[:len(header.Extra)-65], + header.MixDigest, + header.Nonce, + }) + hasher.Sum(hash[:0]) + return hash.Bytes(), nil +} + +// SignTypedData signs EIP712 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 := hashStruct(domainTypes, typedData.Domain.Map(), "EIP712Domain", 0) + domainSeparator := common.Bytes2Hex(domainSeparatorBytes) //if err != nil { // return nil, err //} - delete(data.Types, "EIP712Domain") - typedDataHash := hashStruct(data.Types, data.PrimaryType, data.Message, 0) + delete(typedData.Types, "EIP712Domain") + typedDataHashBytes := hashStruct(typedData.Types, typedData.Message, typedData.PrimaryType, 0) + typedDataHash := common.Bytes2Hex(typedDataHashBytes) //if err != nil { // return nil, err //} - fmt.Println("domainSeparator", domainSeparator.String()) - fmt.Println("typedDataHash", typedDataHash.String()) - return common.FromHex("0xdeadbeef"), nil + fmt.Println("domainSeparator", domainSeparator) + fmt.Println("typedDataHash", typedDataHash) + + var buffer bytes.Buffer + buffer.WriteString("\x19\x01") + buffer.WriteString(domainSeparator) + buffer.WriteString(typedDataHash) + + sighash := crypto.Keccak256(buffer.Bytes()) + msg := fmt.Sprintf("\x19\\x%xEthereum Signed Message\n:%s%s", DataTyped.ByteVersion, domainSeparator, typedDataHash) + //req := &SignDataRequest{Rawdata: typedData, Message: msg, Hash: sighash, ContentType: DataTyped.Mime} + + var req, err = api.determineSignatureFormat(contentType, data) + if err != nil { + return nil, err + } + + signature, err := api.Sign(ctx, addr, req) + if err != nil { + api.UI.ShowError(err.Error()) + return nil, err + } + + return signature, nil + + return crypto.Keccak256(buffer.Bytes()), nil } // hashStruct generates the following encoding for the given domain and message: // `encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01" β€– domainSeparator β€– hashStruct(message)` -func hashStruct(types map[string]EIP712Type, key string, data EIP712Data, depth int) common.Hash { +func hashStruct(_types EIP712Types, data EIP712Data, dataType string, depth int) []byte { helpers.PrintJson("hashStruct", map[string]interface{}{ "depth": depth, }) - typeEncoding := encodeType(types) - typeHash := hex.EncodeToString(crypto.Keccak256([]byte(typeEncoding))) + typeEncoding := encodeType(_types) + typeHash := hex.EncodeToString(crypto.Keccak256(typeEncoding)) - dataEncoding := encodeData(types, key, data, depth) - dataHash := hex.EncodeToString(crypto.Keccak256([]byte(dataEncoding))) + dataEncoding := encodeData(_types, data, dataType, depth) + dataHash := hex.EncodeToString(crypto.Keccak256(dataEncoding)) var buffer bytes.Buffer buffer.WriteString(typeHash) buffer.WriteString(dataHash) - hash := common.BytesToHash(crypto.Keccak256(buffer.Bytes())) + encoding := crypto.Keccak256(buffer.Bytes()) if depth == 0 { - fmt.Printf("typeEncoding %s\n", typeEncoding) - fmt.Printf("dataEncoding %s\n", dataEncoding) + fmt.Printf("typeEncoding %s\n", common.Bytes2Hex(typeEncoding)) + fmt.Printf("dataEncoding %s\n", common.Bytes2Hex(dataEncoding)) } - return hash + + 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 encodeType(types map[string]EIP712Type) string { - helpers.PrintJson("hashStruct", map[string]interface{}{ - "types": types, +func encodeType(_types EIP712Types) []byte { + helpers.PrintJson("encodeType", map[string]interface{}{ + "types": _types, }) + //fmt.Printf("encodeType: types %v\n\n", types) var priorities = make(map[string]uint) - for key := range types { + for key := range _types { priorities[key] = 0 } @@ -119,7 +301,7 @@ func encodeType(types map[string]EIP712Type) string { priorities[typeVal]++ // Importantly, we also have to check for parent types to increment them too - for _, typeObj := range types[typeVal] { + for _, typeObj := range _types[typeVal] { _typeVal := typeObj["type"] firstChar := []rune(_typeVal)[0] @@ -139,32 +321,22 @@ func encodeType(types map[string]EIP712Type) string { return false } - for typeKey, typeArr := range types { + for typeKey, typeArr := range _types { var typeValArr []string for _, typeObj := range typeArr { typeVal := typeObj["type"] - if typeKey == typeVal { - panic(fmt.Errorf("type %s cannot reference itself", typeVal)) - } + //if typeKey == typeVal { + // panic(fmt.Errorf("type %s cannot reference itself", typeVal)) + //} firstChar := []rune(typeVal)[0] if unicode.IsUpper(firstChar) { - if types[typeVal] != nil { + if _types[typeVal] != nil { if !visited(typeValArr, typeVal) { typeValArr = append(typeValArr, typeVal) update(typeKey, typeVal) } - } else { - panic(fmt.Errorf("referenced type %s is undefined", typeVal)) - } - } else { - if !isStandardType(typeVal) { - if types[typeVal] != nil { - panic(fmt.Errorf("Custom type %s must be capitalized", typeVal)) - } else { - panic(fmt.Errorf("Unknown type %s", typeVal)) - } } } } @@ -176,7 +348,7 @@ func encodeType(types map[string]EIP712Type) string { var buffer bytes.Buffer for _, priority := range sortedPriorities { typeKey := priority.Type - typeArr := types[typeKey] + typeArr := _types[typeKey] buffer.WriteString(typeKey) buffer.WriteString("(") @@ -192,64 +364,183 @@ func encodeType(types map[string]EIP712Type) string { buffer.WriteString(")") } - return buffer.String() + return buffer.Bytes() +} + +// 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 + }) + + //for _, priority := range priorities { + // fmt.Printf("%s, Value %d\n", priority.Type, priority.Value) + //} + //fmt.Printf("\n") + + return priorities } // 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, +func encodeData(_types EIP712Types, data interface{}, dataType string, depth int) []byte { + helpers.PrintJson("encodeData", map[string]interface{}{ + "dataType": dataType, + "data": data, "depth": depth, }) var buffer bytes.Buffer - switch val.(type) { - case EIP712Data: - for mapKey, mapVal := range val.(EIP712Data) { + // TODO regex + // handle arrays + if strings.Contains(dataType, "[]") { + arrayVal := data.([]interface{}) + dataType := "TODO" + + var arrayBuffer bytes.Buffer + for obj := range arrayVal { + objEncoding := encodeData(_types, obj, dataType, depth+1) + arrayBuffer.Write(objEncoding) + } + + 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{}) { - hash := hashStruct(types, mapKey, mapVal.(EIP712Data), depth+1) - buffer.WriteString(hash.String()) + data := mapVal.(map[string]interface{}) + encoding := hashStruct(_types, data, nextDataType, depth+1) + buffer.Write(encoding) } else { - str := encodeData(types, mapKey, mapVal, depth+1) - buffer.WriteString(str) + encoding := encodeData(_types, mapVal, nextDataType, depth+1) + buffer.Write(encoding) } } - break + 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() + } - case bool: - boolVal, _ := val.(bool) + // 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 } - encodedVal := abi.U256(big.NewInt(int64Val)) - fmt.Printf("bool encoded value:", encodedVal) - buffer.Write(encodedVal) + encoding := abi.U256(big.NewInt(int64Val)) + buffer.Write(encoding) break - - case string: - bytesVal := common.FromHex(val.(string)) - hash := common.BytesToHash(crypto.Keccak256(bytesVal)) - buffer.WriteString(hash.String()) + case TypeString: + bytesVal := common.FromHex(data.(string)) + encoding := crypto.Keccak256(bytesVal) + buffer.Write(encoding) break - default: - arr := [...]string{"(a)", "(b)", "(c)"} - rand.Seed(time.Now().UnixNano()) - buffer.WriteString(arr[rand.Intn(3)]) break } - return buffer.String() + return buffer.Bytes() +} + +// 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 { + Types EIP712Types `json:"types"` + PrimaryType string `json:"primaryType"` + Domain EIP712Domain `json:"domain"` + Message EIP712Data `json:"message"` + } + + var raw input + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + if raw.Types == nil { + return errors.New("types are undefined") + } + if err := raw.Types.IsValid(); err != nil { + return err + } + typedData.Types = raw.Types + + if raw.Types["EIP712Domain"] == nil { + return errors.New("domain types are undefined") + } + if err := raw.Domain.IsValid(); err != nil { + return err + } + typedData.Domain = raw.Domain + + if len(raw.PrimaryType) == 0 { + return errors.New("primary type is undefined") + } + typedData.PrimaryType = raw.PrimaryType + + if raw.Message == nil { + return errors.New("message is undefined") + } + typedData.Message = raw.Message + + return nil } // isStandardType checks if the given type is a EIP712 conformant type -func isStandardType(typeStr string) bool { +func isStandardTypeStr(typeStr string) bool { standardTypes := []string{ "array", "address", @@ -267,28 +558,33 @@ func isStandardType(typeStr string) bool { return false } -// 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 - }) - for _, priority := range priorities { - fmt.Printf("%s, Value %d\n", priority.Type, priority.Value) - } - fmt.Printf("\n") +// IsValid checks if the given types object is conformant to the specs +func (types *EIP712Types) IsValid() error { + for typeKey, typeArr := range (*types) { + for _, typeObj := range typeArr { + typeVal := typeObj["type"] + if typeKey == typeVal { + panic(fmt.Errorf("type %s cannot reference itself", typeVal)) + } - return priorities + firstChar := []rune(typeVal)[0] + if unicode.IsUpper(firstChar) { + if (*types)[typeVal] == nil { + return fmt.Errorf("referenced type %s is undefined", typeVal) + } + } else { + if !isStandardTypeStr(typeVal) { + if (*types)[typeVal] != nil { + return fmt.Errorf("custom type %s must be capitalized", typeVal) + } else { + return fmt.Errorf("unknown type %s", typeVal) + } + } + } + } + } + return nil } // IsValid checks if the given domain is valid, i.e. contains at least @@ -307,12 +603,63 @@ func (domain *EIP712Domain) IsValid() error { // 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, - "version": domain.Version, - "chainId": domain.Name, - "verifyingContract": domain.VerifyingContract, - "salt": domain.Salt, +func (domain *EIP712Domain) Map() EIP712Data { + dataMap := EIP712Data{ + "chainId": domain.ChainId, } + + if len(domain.Name) > 0 { + dataMap["name"] = domain.Name + } + + if len(domain.Version) > 0 { + dataMap["version"] = domain.Version + } + + if len(domain.VerifyingContract) > 0 { + dataMap["verifyingContract"] = domain.VerifyingContract + } + + if len(domain.Salt) > 0 { + dataMap["salt"] = domain.Salt + } + return dataMap } + +// Determines the content type and then recovers the address associated with the given sig +func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { + mediaType, _, err := mime.ParseMediaType(contentType) + if err != nil { + return common.Address{}, err + } + switch mediaType { + case TextPlain.Mime: + // 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 + // the address of: + // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") + // addr = ecrecover(hash, signature) + // + // Note, the signature must conform to the secp256k1 curve R, S and V values, where + // the V value must be be 27 or 28 for legacy reasons. + // + // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover + + if len(sig) != 65 { + return common.Address{}, fmt.Errorf("signature must be 65 bytes long") + } + if sig[64] != 27 && sig[64] != 28 { + return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") + } + sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 + hash, _ := signTextPlain(data) + rpk, err := crypto.SigToPub(hash, sig) + if err != nil { + return common.Address{}, err + } + return crypto.PubkeyToAddress(*rpk), nil + default: + return common.Address{}, fmt.Errorf("content type '%s' not implemented for ecRecover", contentType) + } +} \ No newline at end of file From 5cba9e23560ebf3fd3e28fe14fef0a5eb74d1ba6 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Tue, 23 Oct 2018 18:39:56 +0100 Subject: [PATCH 14/84] Ran goimports and gofmt --- cmd/clef/audit.log | 57 ++++++ signer/core/abihelper.go | 7 +- signer/core/abihelper_test.go | 5 +- signer/core/cliui.go | 1 - signer/core/signed_data.go | 346 +++++++++++++++++----------------- signer/core/types.go | 3 +- 6 files changed, 239 insertions(+), 180 deletions(-) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log index 667d0443b4f6..a38552de7fcd 100644 --- a/cmd/clef/audit.log +++ b/cmd/clef/audit.log @@ -146,3 +146,60 @@ t=2018-10-20T19:14:32+0100 lvl=info msg=SignTypedData api=signer type=response d t=2018-10-20T19:36:35+0100 lvl=info msg=Configured api=signer audit log=audit.log t=2018-10-20T19:36:40+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61953\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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-20T19:36:40+0100 lvl=info msg=SignTypedData api=signer type=response data=7951623617a41fec181fafc3555c0a494d01dce0408b6596964f5f50acba239e error=nil +t=2018-10-21T14:58:59+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T15:00:47+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T15:00:47+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52242\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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-21T15:05:18+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T15:07:18+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T15:07:20+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52313\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] Person:[map[name:name type:string] map[type:address name:wallet]]] 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-21T15:07:45+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T15:07:48+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52326\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-21T15:08:15+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T15:08:18+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52339\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:uint256 name:chainId] 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-21T15:08:46+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T15:08:51+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52351\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-21T15:09:58+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52360\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-21T15:10:10+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T15:10:23+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52371\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:string name:contents]]] 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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-21T15:11:27+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52373\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=deadbeef content-type=text/plain +t=2018-10-21T15:47:19+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T15:54:17+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:53039\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"Apache-HttpClient/4.5.5 (Java/1.8.0_152-release)\",\"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:[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-21T18:53:59+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T18:54:53+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T18:54:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54450\",\"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:[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-21T19:00:29+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:00:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54496\",\"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[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:[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-21T19:01:35+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:01:45+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54509\",\"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[type:Person name:from] map[type:Person name:to] map[type:string name:contents]] 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-21T19:06:53+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:07:33+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:07:56+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54595\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66D76e6A80DC7D46d7EC1b79b15Dfa34c3C6eF21 [chksum ok]" data="{Types:map[Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]] EIP712Domain:[map[name:name type:string] map[type:string name:version] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet 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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-21T19:09:56+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:09:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54620\",\"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[type:address name:verifyingContract]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[type:Person name:to] map[type:string name:contents]]] 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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-21T19:10:23+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:10:46+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54635\",\"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:[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-21T19:16:04+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:16:11+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54666\",\"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[type:uint256 name:chainId] 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[from:map[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-21T19:19:07+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:19:12+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54690\",\"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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-21T19:20:49+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:20:54+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54713\",\"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:[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-21T19:53:52+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:54:07+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54849\",\"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:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" +t=2018-10-21T19:55:14+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:55:19+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54863\",\"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[type:string name:version] map[name:chainId type:uint256] map[type:address name:verifyingContract]] 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:[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-21T19:56:13+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:56:18+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54874\",\"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[type:uint256 name:chainId] map[type:address name:verifyingContract]] 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:[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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-21T19:57:02+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:57:41+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:57:43+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54896\",\"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[type:string name:version] 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:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-21T19:58:03+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:58:04+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54908\",\"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[type:uint256 name:chainId] map[type:address name:verifyingContract]] 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:[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-21T19:58:39+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:58:44+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54920\",\"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:[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-21T20:00:09+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T20:00:34+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54933\",\"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[type:address name:verifyingContract]] 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:[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:03:51+0100 lvl=info msg=Configured api=signer audit log=audit.log +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]]}" diff --git a/signer/core/abihelper.go b/signer/core/abihelper.go index 0fef24939549..de6b815a68f2 100644 --- a/signer/core/abihelper.go +++ b/signer/core/abihelper.go @@ -17,17 +17,16 @@ package core import ( + "bytes" "encoding/json" "fmt" "io/ioutil" + "os" + "regexp" "strings" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - - "bytes" - "os" - "regexp" ) type decodedArgument struct { diff --git a/signer/core/abihelper_test.go b/signer/core/abihelper_test.go index 878210be1f21..4a3a2f06d29f 100644 --- a/signer/core/abihelper_test.go +++ b/signer/core/abihelper_test.go @@ -18,12 +18,11 @@ package core import ( "fmt" - "strings" - "testing" - "io/ioutil" "math/big" "reflect" + "strings" + "testing" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" diff --git a/signer/core/cliui.go b/signer/core/cliui.go index 940f1f43aadb..7fefaabd7589 100644 --- a/signer/core/cliui.go +++ b/signer/core/cliui.go @@ -21,7 +21,6 @@ import ( "fmt" "os" "strings" - "sync" "github.com/davecgh/go-spew/spew" diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index eef2c89b3c96..bf12a3ba4588 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -7,10 +7,7 @@ import ( "encoding/json" "errors" "fmt" - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto/sha3" - "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/common/math" "math/big" "mime" "reflect" @@ -18,18 +15,21 @@ import ( "strings" "unicode" - "github.com/PaulRBerg/basics/helpers" + "github.com/ethereum/go-ethereum/accounts" "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/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/sha3" + "github.com/ethereum/go-ethereum/rlp" ) type TypedData struct { - Types EIP712Types `json:"types"` - PrimaryType string `json:"primaryType"` - Domain EIP712Domain `json:"domain"` - Message EIP712Data `json:"message"` + Types EIP712Types `json:"types"` + PrimaryType string `json:"primaryType"` + Domain EIP712Domain `json:"domain"` + Message EIP712Data `json:"message"` } type EIP712Type []map[string]string @@ -44,20 +44,19 @@ 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 common.Address `json:"verifyingContract"` + Salt hexutil.Bytes `json:"salt"` } const ( - TypeArray = "array" - TypeAddress = "address" - TypeBool = "bool" - TypeBytes = "bytes" - TypeInt = "int" - TypeString = "string" + TypeAddress = "address" + TypeBool = "bool" + TypeBytes = "bytes" + TypeInt = "int" + TypeString = "string" ) // Sign receives a request and produces a signature @@ -215,57 +214,52 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd domainTypes := EIP712Types{ "EIP712Domain": typedData.Types["EIP712Domain"], } - domainSeparatorBytes := hashStruct(domainTypes, typedData.Domain.Map(), "EIP712Domain", 0) - domainSeparator := common.Bytes2Hex(domainSeparatorBytes) - //if err != nil { - // return nil, err - //} - delete(typedData.Types, "EIP712Domain") - typedDataHashBytes := hashStruct(typedData.Types, typedData.Message, typedData.PrimaryType, 0) - typedDataHash := common.Bytes2Hex(typedDataHashBytes) - //if err != nil { - // return nil, err - //} - - fmt.Println("domainSeparator", domainSeparator) - fmt.Println("typedDataHash", typedDataHash) + domainSeparatorBytes := typedData.hashStruct(domainTypes, typedData.Domain.Map(), "EIP712Domain", 0) + domainSeparator := common.BytesToHash(domainSeparatorBytes) - var buffer bytes.Buffer - buffer.WriteString("\x19\x01") - buffer.WriteString(domainSeparator) - buffer.WriteString(typedDataHash) - - sighash := crypto.Keccak256(buffer.Bytes()) - msg := fmt.Sprintf("\x19\\x%xEthereum Signed Message\n:%s%s", DataTyped.ByteVersion, domainSeparator, typedDataHash) - //req := &SignDataRequest{Rawdata: typedData, Message: msg, Hash: sighash, ContentType: DataTyped.Mime} + 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) - var req, err = api.determineSignatureFormat(contentType, data) + 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} signature, err := api.Sign(ctx, addr, req) if err != nil { api.UI.ShowError(err.Error()) return nil, err } - return signature, nil - - return crypto.Keccak256(buffer.Bytes()), nil } // hashStruct generates the following encoding for the given domain and message: // `encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01" β€– domainSeparator β€– hashStruct(message)` -func hashStruct(_types EIP712Types, data EIP712Data, dataType string, depth int) []byte { - helpers.PrintJson("hashStruct", map[string]interface{}{ - "depth": depth, - }) - - typeEncoding := encodeType(_types) +func (typedData *TypedData) hashStruct(_types EIP712Types, data EIP712Data, dataType string, depth int) []byte { + typeEncoding := typedData.encodeType(_types) typeHash := hex.EncodeToString(crypto.Keccak256(typeEncoding)) - dataEncoding := encodeData(_types, data, dataType, depth) + dataEncoding := typedData.encodeData(_types, data, dataType, depth) dataHash := hex.EncodeToString(crypto.Keccak256(dataEncoding)) var buffer bytes.Buffer @@ -278,6 +272,10 @@ func hashStruct(_types EIP712Types, data EIP712Data, dataType string, depth int) fmt.Printf("dataEncoding %s\n", common.Bytes2Hex(dataEncoding)) } + printJson("hashStruct", map[string]interface{}{ + "depth": depth, + "encoding": buffer.String(), + }) return encoding } @@ -285,12 +283,7 @@ func hashStruct(_types EIP712Types, data EIP712Data, dataType string, depth int) // `name β€– "(" β€– member₁ β€– "," β€– memberβ‚‚ β€– "," β€– … β€– memberβ‚™ ")"` // // each member is written as `type β€– " " β€– name` encodings cascade down and are sorted by name -func encodeType(_types EIP712Types) []byte { - helpers.PrintJson("encodeType", map[string]interface{}{ - "types": _types, - }) - //fmt.Printf("encodeType: types %v\n\n", types) - +func (typedData *TypedData) encodeType(_types EIP712Types) []byte { var priorities = make(map[string]uint) for key := range _types { priorities[key] = 0 @@ -313,8 +306,8 @@ func encodeType(_types EIP712Types) []byte { // Checks if referenced type has already been visited to optimise algo visited := func(arr []string, val string) bool { - for _, elem := range arr { - if elem == val { + for _, obj := range arr { + if obj == val { return true } } @@ -323,27 +316,22 @@ func encodeType(_types EIP712Types) []byte { for typeKey, typeArr := range _types { var typeValArr []string - for _, typeObj := range typeArr { typeVal := typeObj["type"] - //if typeKey == typeVal { - // panic(fmt.Errorf("type %s cannot reference itself", typeVal)) - //} - firstChar := []rune(typeVal)[0] - if unicode.IsUpper(firstChar) { - if _types[typeVal] != nil { - if !visited(typeValArr, typeVal) { - typeValArr = append(typeValArr, typeVal) - update(typeKey, typeVal) - } - } + // filtering the structs from the primitives + if _types[typeVal] != nil && !visited(typeValArr, typeVal) { + typeValArr = append(typeValArr, typeVal) + update(typeKey, typeVal) } } - typeValArr = []string{} } + if _types[typedData.PrimaryType] != nil { + priorities[typedData.PrimaryType] = math.MaxInt32 + } + sortedPriorities := sortByPriorityAndName(priorities) var buffer bytes.Buffer for _, priority := range sortedPriorities { @@ -364,44 +352,18 @@ func encodeType(_types EIP712Types) []byte { buffer.WriteString(")") } - return buffer.Bytes() -} - -// 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 + printJson("encodeType", map[string]interface{}{ + "types": _types, + "encoding": buffer.String(), }) - - //for _, priority := range priorities { - // fmt.Printf("%s, Value %d\n", priority.Type, priority.Value) - //} - //fmt.Printf("\n") - - return priorities + return buffer.Bytes() } // encodeData generates the following encoding: // `enc(value₁) β€– enc(valueβ‚‚) β€– … β€– enc(valueβ‚™)` // // each encoded member is 32-byte long -func encodeData(_types EIP712Types, data interface{}, dataType string, depth int) []byte { - helpers.PrintJson("encodeData", map[string]interface{}{ - "dataType": dataType, - "data": data, - "depth": depth, - }) - +func (typedData *TypedData) encodeData(_types EIP712Types, data interface{}, dataType string, depth int) []byte { var buffer bytes.Buffer // TODO regex @@ -412,7 +374,7 @@ func encodeData(_types EIP712Types, data interface{}, dataType string, depth int var arrayBuffer bytes.Buffer for obj := range arrayVal { - objEncoding := encodeData(_types, obj, dataType, depth+1) + objEncoding := typedData.encodeData(_types, obj, dataType, depth+1) arrayBuffer.Write(objEncoding) } @@ -428,10 +390,10 @@ func encodeData(_types EIP712Types, data interface{}, dataType string, depth int nextDataType := findNextDataType(_types, dataType, mapKey) if reflect.TypeOf(mapVal) == reflect.TypeOf(EIP712Data{}) { data := mapVal.(map[string]interface{}) - encoding := hashStruct(_types, data, nextDataType, depth+1) + encoding := typedData.hashStruct(_types, data, nextDataType, depth+1) buffer.Write(encoding) } else { - encoding := encodeData(_types, mapVal, nextDataType, depth+1) + encoding := typedData.encodeData(_types, mapVal, nextDataType, depth+1) buffer.Write(encoding) } } @@ -479,9 +441,72 @@ func encodeData(_types EIP712Types, data interface{}, dataType string, depth int break } + printJson("encodeData", map[string]interface{}{ + "dataType": dataType, + "data": data, + "depth": depth, + "encoding": buffer.String(), + }) return buffer.Bytes() } +// Determines the content type and then recovers the address associated with the given sig +func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { + mediaType, _, err := mime.ParseMediaType(contentType) + if err != nil { + return common.Address{}, err + } + switch mediaType { + case TextPlain.Mime: + // 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 + // the address of: + // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") + // addr = ecrecover(hash, signature) + // + // Note, the signature must conform to the secp256k1 curve R, S and V values, where + // the V value must be be 27 or 28 for legacy reasons. + // + // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover + + if len(sig) != 65 { + return common.Address{}, fmt.Errorf("signature must be 65 bytes long") + } + if sig[64] != 27 && sig[64] != 28 { + return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") + } + sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 + hash, _ := signTextPlain(data) + rpk, err := crypto.SigToPub(hash, sig) + if err != nil { + return common.Address{}, err + } + return crypto.PubkeyToAddress(*rpk), nil + default: + return common.Address{}, fmt.Errorf("content type '%s' not implemented for ecRecover", contentType) + } +} + +// 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 { @@ -499,10 +524,10 @@ func findNextDataType(_types EIP712Types, mapType string, mapKey string) string // UnmarshalJSON validates the input data func (typedData *TypedData) UnmarshalJSON(data []byte) error { type input struct { - Types EIP712Types `json:"types"` - PrimaryType string `json:"primaryType"` - Domain EIP712Domain `json:"domain"` - Message EIP712Data `json:"message"` + Types EIP712Types `json:"types"` + PrimaryType string `json:"primaryType"` + Domain EIP712Domain `json:"domain"` + Message EIP712Data `json:"message"` } var raw input @@ -539,29 +564,21 @@ func (typedData *TypedData) UnmarshalJSON(data []byte) error { return nil } -// isStandardType checks if the given type is a EIP712 conformant type -func isStandardTypeStr(typeStr string) bool { - standardTypes := []string{ - "array", - "address", - "boolean", - "bytes", - "string", - "struct", - "uint", +// Map is a helper function to generate a map version of the typed data +func (typedData *TypedData) Map() map[string]interface{} { + dataMap := map[string]interface{}{ + "Types": typedData.Types, + "Domain": typedData.Domain.Map(), + "PrimaryType": typedData.PrimaryType, + "Message": typedData.Message, } - for _, val := range standardTypes { - if strings.HasPrefix(typeStr, val) { - return true - } - } - return false -} + return dataMap +} // IsValid checks if the given types object is conformant to the specs func (types *EIP712Types) IsValid() error { - for typeKey, typeArr := range (*types) { + for typeKey, typeArr := range *types { for _, typeObj := range typeArr { typeVal := typeObj["type"] if typeKey == typeVal { @@ -587,6 +604,23 @@ func (types *EIP712Types) IsValid() error { return nil } +// isStandardType checks if the given type is a EIP712 conformant type +func isStandardTypeStr(typeStr string) bool { + standardTypes := []string{ + TypeAddress, + TypeBool, + TypeBytes, + TypeInt, + TypeString, + } + for _, val := range standardTypes { + if strings.HasPrefix(typeStr, val) || strings.Contains(typeStr, val) { + return true + } + } + return false +} + // IsValid checks if the given domain is valid, i.e. contains at least // the minimum viable keys and values func (domain *EIP712Domain) IsValid() error { @@ -601,11 +635,10 @@ func (domain *EIP712Domain) IsValid() error { return nil } -// Values is a helper function to return the values of a domain as a map -// with arbitrary values -func (domain *EIP712Domain) Map() EIP712Data { - dataMap := EIP712Data{ - "chainId": domain.ChainId, +// Map is a helper function to generate a map version of the domain +func (domain *EIP712Domain) Map() map[string]interface{} { + dataMap := map[string]interface{}{ + "chainId": domain.ChainId, } if len(domain.Name) > 0 { @@ -626,40 +659,13 @@ func (domain *EIP712Domain) Map() EIP712Data { return dataMap } -// Determines the content type and then recovers the address associated with the given sig -func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { - mediaType, _, err := mime.ParseMediaType(contentType) +// PrintJson will be removed +func printJson(label string, output map[string]interface{}) { + jsonVal, err := json.MarshalIndent(output, "", " ") if err != nil { - return common.Address{}, err + panic(err) } - switch mediaType { - case TextPlain.Mime: - // 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 - // the address of: - // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") - // addr = ecrecover(hash, signature) - // - // Note, the signature must conform to the secp256k1 curve R, S and V values, where - // the V value must be be 27 or 28 for legacy reasons. - // - // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover - - if len(sig) != 65 { - return common.Address{}, fmt.Errorf("signature must be 65 bytes long") - } - if sig[64] != 27 && sig[64] != 28 { - return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") - } - sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 - hash, _ := signTextPlain(data) - rpk, err := crypto.SigToPub(hash, sig) - if err != nil { - return common.Address{}, err - } - return crypto.PubkeyToAddress(*rpk), nil - default: - return common.Address{}, fmt.Errorf("content type '%s' not implemented for ecRecover", contentType) - } -} \ No newline at end of file + fmt.Printf("%s:", label) + fmt.Print(string(jsonVal)) + fmt.Print("\n\n") +} diff --git a/signer/core/types.go b/signer/core/types.go index 128055774858..8acfa7a6afd4 100644 --- a/signer/core/types.go +++ b/signer/core/types.go @@ -19,9 +19,8 @@ package core import ( "encoding/json" "fmt" - "strings" - "math/big" + "strings" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" From 2b222273f73d1cb74e0600db039087eabc8c864e Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Tue, 23 Oct 2018 20:17:36 +0100 Subject: [PATCH 15/84] Drafted first version of EIP-712, including tests --- cmd/clef/audit.log | 19 ++ cmd/clef/main.go | 4 +- signer/core/api_test.go | 66 ----- signer/core/signed_data.go | 412 ++++++++++++++------------------ signer/core/signed_data_test.go | 190 +++++++++++++++ 5 files changed, 397 insertions(+), 294 deletions(-) create mode 100644 signer/core/signed_data_test.go diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log index a38552de7fcd..652dc04ae86a 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 dca06e9a7ee3..0d56149590cb 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 f1695feec935..ff68c0b4e1f0 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 bf12a3ba4588..2b74cb62eca2 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 - - // TODO regex - // handle arrays - if strings.Contains(dataType, "[]") { - arrayVal := data.([]interface{}) - dataType := "TODO" - - var arrayBuffer bytes.Buffer - for obj := range arrayVal { - objEncoding := typedData.encodeData(_types, obj, dataType, depth+1) - arrayBuffer.Write(objEncoding) +func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}) []byte { + encTypes := []string{} + encValues := []interface{}{} + + // Add typehash + encTypes = append(encTypes, "bytes32") + encValues = append(encValues, typedData.TypeHash(primaryType)) + + // Handle primitive values + handlePrimitiveValue := func(encType string, encValue interface{}) (string, interface{}) { + var primitiveEncType string + var primitiveEncValue interface{} + + 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 } - - 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) + return primitiveEncType, primitiveEncValue + } + + // 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) } - 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) - break - case TypeBool: - boolVal, _ := data.(bool) - var int64Val int64 - if boolVal { - int64Val = 1 - } - 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 + buffer := bytes.Buffer{} + for _, encValue := range encValues { + buffer.Write(bytesValueOf(encValue)) } - printJson("encodeData", map[string]interface{}{ - "dataType": dataType, - "data": data, - "depth": depth, - "encoding": buffer.String(), - }) - return buffer.Bytes() + 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 000000000000..58502d00801f --- /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 From be2b37bafbf5093e07f55dc53d921abb329da1c4 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Wed, 24 Oct 2018 03:16:35 +0100 Subject: [PATCH 16/84] Temporarily switched to using common.Address in tests --- signer/core/signed_data.go | 10 +++++----- signer/core/signed_data_test.go | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 2b74cb62eca2..9795a8d3c662 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -61,7 +61,7 @@ type EIP712Domain struct { Name string `json:"name"` Version string `json:"version"` ChainId *big.Int `json:"chainId"` - VerifyingContract string `json:"verifyingContract"` + VerifyingContract common.Address `json:"verifyingContract"` Salt hexutil.Bytes `json:"salt"` } @@ -358,14 +358,14 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter switch encType { case "address": - primitiveEncType = "address" + primitiveEncType = "uint160" 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))) { + //foo := common.BytesToAddress([]byte(encValue.(string))) + //fmt.Println(foo) + for _, _byte := range encValue.(common.Address) { bytesValue = append(bytesValue, _byte) } primitiveEncValue = bytesValue diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 58502d00801f..a41fbcd130e2 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -76,18 +76,18 @@ var domainStandard = EIP712Domain{ "Ether Mail", "1", big.NewInt(1), - "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + common.HexToAddress("0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"), nil, } var dataStandard = map[string]interface{}{ "from": map[string]interface{}{ "name": "Cow", - "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + "wallet": common.HexToAddress("0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"), }, "to": map[string]interface{}{ "name": "Bob", - "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + "wallet": common.HexToAddress("0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"), }, "contents": "Hello, Bob!", } From 6910ba299137ec569fc1920fcbbfba7e24e56857 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Thu, 25 Oct 2018 14:41:04 +0200 Subject: [PATCH 17/84] Drafted text/validator and and rewritten []byte as hexutil.Bytes --- cmd/clef/audit.log | 23 +++++++++ signer/core/signed_data.go | 102 ++++++++++++++++++++----------------- 2 files changed, 79 insertions(+), 46 deletions(-) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log index 652dc04ae86a..45814ac8cfbd 100644 --- a/cmd/clef/audit.log +++ b/cmd/clef/audit.log @@ -222,3 +222,26 @@ t=2018-10-23T19:51:57+0100 lvl=info msg=Configured api=signer audit log=audit.lo 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!]}" +t=2018-10-25T13:47:17+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T13:47:54+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T13:50:09+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50039\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T13:51:00+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T13:51:07+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:51792\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T13:57:27+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T13:57:30+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:62734\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T14:01:29+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T14:01:30+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:53871\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608adeadbeef content-type=text/validator +t=2018-10-25T14:02:00+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T14:02:10+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:55131\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608adeadbeef content-type=text/validator +t=2018-10-25T14:13:15+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T14:13:16+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58361\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T14:13:53+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T14:14:04+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59928\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T14:14:39+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T14:14:43+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61106\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T14:21:56+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T14:22:59+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59342\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=60deadbeef content-type=text/validator +t=2018-10-25T14:22:59+0200 lvl=info msg=SignData api=signer type=response data= error="validator address and data undefined" +t=2018-10-25T14:23:05+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59342\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T14:23:31+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T14:23:55+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61180\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 9795a8d3c662..548438d355ae 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -39,6 +39,11 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) +type ValidatorData struct { + Address common.Address + Message hexutil.Bytes +} + type TypedData struct { Types EIP712Types `json:"types"` PrimaryType string `json:"primaryType"` @@ -77,7 +82,7 @@ const ( // Note, the produced signature conforms to the secp256k1 curve R, S and V values, // where the V value will be 27 or 28 for legacy reasons. -func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, req *SignDataRequest) ([]byte, error) { +func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, req *SignDataRequest) (hexutil.Bytes, error) { req.Address = addr req.Meta = MetadataFromContext(ctx) @@ -110,7 +115,7 @@ func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, re // // Different types of validation occur. func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { - var req, err = api.determineSignatureFormat(contentType, data) + var req, err = api.determineSignatureFormat(contentType, addr, data) if err != nil { return nil, err } @@ -125,7 +130,11 @@ func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr com } // Determines which signature method should be used based upon the mime type -func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil.Bytes) (*SignDataRequest, error) { +// In the cases where it matters ensure that the charset is handled. The charset +// resides in the 'params' returned as the second returnvalue from mime.ParseMediaType +// charset, ok := params["charset"] +// As it is now, we accept any charset and just treat it as 'raw'. +func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (*SignDataRequest, error) { var req *SignDataRequest mediaType, _, err := mime.ParseMediaType(contentType) if err != nil { @@ -135,18 +144,19 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil. switch mediaType { case TextValidator.Mime: // Data with an intended validator - sighash, msg := signTextWithValidator(data) + if len(data) < common.AddressLength { + return nil, errors.New("validator address and data undefined") + } + if len(data) == common.AddressLength { + return nil, errors.New("no data to sign") + } + sighash, msg := SignTextValidator(data) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} break case TextPlain.Mime: // Sign calculates an Ethereum ECDSA signature for: // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") - - // In the cases where it matters ensure that the charset is handled. The charset - // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType - // charset, ok := params["charset"] - // As it is now, we accept any charset and just treat it as 'raw'. - sighash, msg := signTextPlain(data) + sighash, msg := SignTextPlain(data) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} break case ApplicationClique.Mime: @@ -155,11 +165,11 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil. if err := rlp.DecodeBytes(data, header); err != nil { return nil, err } - sighash, err := signCliqueHeader(header) + sighash, err := SignCliqueHeader(header) if err != nil { return nil, err } - msg := fmt.Sprintf("Clique block %d [0x%x]", header.Number, header.Hash()) + msg := fmt.Sprintf("clique block %d [0x%x]", header.Number, header.Hash()) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} break default: @@ -169,23 +179,15 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil. } -// signTextPlain is a helper function that calculates a hash for the given message that can be -// safely used to calculate a signature from. -// -// The hash is calculated as -// keccak256("\x19${byteVersion}Ethereum Signed Message:\n"${message length}${message}). -// -// This gives context to the signed message and prevents signing of transactions. -func signTextPlain(data []byte) ([]byte, string) { - msg := fmt.Sprintf("\x19\\x%xEthereum Signed Message:\n%d%s", TextPlain.ByteVersion, len(data), data) - return crypto.Keccak256([]byte(msg)), msg -} - // signTextWithValidator signs the given message which can be further recovered // with the given validator. -func signTextWithValidator(data []byte) ([]byte, string) { - msg := "TODO" - return crypto.Keccak256([]byte(msg)), msg +// +// hash = keccak256("\x19\x00"${address}${data}). +func SignTextValidator(data hexutil.Bytes) (hexutil.Bytes, string) { + address := common.BytesToAddress(data[:common.AddressLength]) + message := data[common.AddressLength:len(data)-1] + hash := fmt.Sprintf("\x19\x00:%x%s", address, string(message)) + return crypto.Keccak256(hexutil.Bytes(hash)), hash } // signCliqueHeader returns the hash which is used as input for the proof-of-authority @@ -195,7 +197,7 @@ func signTextWithValidator(data []byte) ([]byte, string) { // The method requires the extra data to be at least 65 bytes -- the original implementation // in clique.go panics if this is the case, thus it's been reimplemented here to avoid the panic // and simply return an error instead -func signCliqueHeader(header *types.Header) (hexutil.Bytes, error) { +func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { hash := common.Hash{} if len(header.Extra) < 65 { return hash.Bytes(), fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) @@ -222,6 +224,18 @@ func signCliqueHeader(header *types.Header) (hexutil.Bytes, error) { return hash.Bytes(), nil } +// 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 +// signing of transactions. +// +// hash = keccak256("\x19$Ethereum Signed Message:\n"${message length}${message}). +func SignTextPlain(data hexutil.Bytes) (hexutil.Bytes, string) { + // The letter `E` is \x45 in hex, retrofitting + // https://github.com/ethereum/go-ethereum/pull/2940/commits + hash := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data)) + return crypto.Keccak256(hexutil.Bytes(hash)), hash +} + // 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) { @@ -252,12 +266,11 @@ 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(primaryType string, data EIP712Data) []byte { +func (typedData *TypedData) HashStruct(primaryType string, data EIP712Data) hexutil.Bytes { 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 { includes := func(arr []string, str string) bool { for _, obj := range arr { @@ -289,7 +302,7 @@ func (typedData *TypedData) Dependencies(primaryType string, found []string) []s // `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 { +func (typedData *TypedData) EncodeType(primaryType string) hexutil.Bytes { // Get dependencies primary first, then alphabetical deps := typedData.Dependencies(primaryType, []string{}) for i, dep := range deps { @@ -317,33 +330,33 @@ func (typedData *TypedData) EncodeType(primaryType string) []byte { return buffer.Bytes() } -func (typedData *TypedData) TypeHash(primaryType string) []byte { +func (typedData *TypedData) TypeHash(primaryType string) hexutil.Bytes { return crypto.Keccak256(typedData.EncodeType(primaryType)) } -func bytesValueOf(_interface interface{}) []byte { - bytesVal, ok := _interface.([]byte) +func bytesValueOf(_interface interface{}) hexutil.Bytes { + bytesVal, ok := _interface.(hexutil.Bytes) if ok { return bytesVal } switch reflect.TypeOf(_interface) { case reflect.TypeOf(string("")): - return []byte(_interface.(string)) + return hexutil.Bytes(_interface.(string)) break default: break } panic(fmt.Errorf("unrecognized interface %v", _interface)) - return []byte{} + return hexutil.Bytes{} } // encodeData generates the following encoding: // `enc(value₁) β€– enc(valueβ‚‚) β€– … β€– enc(valueβ‚™)` // // each encoded member is 32-byte long -func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}) []byte { +func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}) hexutil.Bytes { encTypes := []string{} encValues := []interface{}{} @@ -359,12 +372,10 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter switch encType { case "address": primitiveEncType = "uint160" - bytesValue := []byte{} + bytesValue := hexutil.Bytes{} for i := 0; i < 12; i++ { bytesValue = append(bytesValue, 0) } - //foo := common.BytesToAddress([]byte(encValue.(string))) - //fmt.Println(foo) for _, _byte := range encValue.(common.Address) { bytesValue = append(bytesValue, _byte) } @@ -387,11 +398,11 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter encTypes = append(encTypes, "bytes32") sizeStr := strings.TrimPrefix(encType, "bytes") size, _ := strconv.Atoi(sizeStr) - bytesValue := []byte{} + bytesValue := hexutil.Bytes{} for i := 0; i < 32-size; i++ { bytesValue = append(bytesValue, 0) } - for _, _byte := range encValue.([]byte) { + for _, _byte := range encValue.(hexutil.Bytes) { bytesValue = append(bytesValue, _byte) } primitiveEncValue = bytesValue @@ -461,7 +472,6 @@ func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data he // the V value must be be 27 or 28 for legacy reasons. // // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover - if len(sig) != 65 { return common.Address{}, fmt.Errorf("signature must be 65 bytes long") } @@ -469,7 +479,7 @@ func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data he return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") } sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 - hash, _ := signTextPlain(data) + hash, _ := SignTextPlain(data) rpk, err := crypto.SigToPub(hash, sig) if err != nil { return common.Address{}, err @@ -481,7 +491,7 @@ func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data he } // UnmarshalJSON validates the input data -func (typedData *TypedData) UnmarshalJSON(data []byte) error { +func (typedData *TypedData) UnmarshalJSON(data hexutil.Bytes) error { type input struct { Types EIP712Types `json:"types"` PrimaryType string `json:"primaryType"` From 61c6704c37dca392dc9b248ab24cfb35af304980 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sat, 27 Oct 2018 10:42:18 +0200 Subject: [PATCH 18/84] Solved stringified address encoding issue --- accounts/accounts.go | 2 +- cmd/clef/audit.log | 9 +++++ signer/core/signed_data.go | 68 +++++++++++++++++++-------------- signer/core/signed_data_test.go | 19 +++++---- 4 files changed, 58 insertions(+), 40 deletions(-) diff --git a/accounts/accounts.go b/accounts/accounts.go index cb1eae281587..a0675ab7a609 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -109,7 +109,7 @@ type Wallet interface { // a password to decrypt the account, or a PIN code to verify the transaction), // an AuthNeededError instance will be returned, containing infos for the user // about which fields or actions are needed. The user may retry by providing - // the needed details via SignTxWithPassphrase, or by other means (e.g. unlock + // the needed details via SignHashWithPassphraseSignTxWithPassphrase, or by other means (e.g. unlock // the account in a keystore). SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log index 45814ac8cfbd..85b2cc1f8d8d 100644 --- a/cmd/clef/audit.log +++ b/cmd/clef/audit.log @@ -245,3 +245,12 @@ t=2018-10-25T14:22:59+0200 lvl=info msg=SignData api=signer type=response data t=2018-10-25T14:23:05+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59342\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator t=2018-10-25T14:23:31+0200 lvl=info msg=Configured api=signer audit log=audit.log t=2018-10-25T14:23:55+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61180\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T20:12:47+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T20:13:00+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59607\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T20:34:51+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T20:34:53+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:62187\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T20:35:38+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T20:35:43+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:62300\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T20:41:50+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T20:42:06+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:63054\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-26T10:33:40+0200 lvl=info msg=Configured api=signer audit log=audit.log diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 548438d355ae..eb5df4d8939b 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -63,11 +63,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 ( @@ -76,6 +76,7 @@ const ( TypeBytes = "bytes" TypeInt = "int" TypeString = "string" + TypeUint = "uint" ) // Sign receives a request and produces a signature @@ -151,6 +152,7 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M return nil, errors.New("no data to sign") } sighash, msg := SignTextValidator(data) + fmt.Printf("%s", sighash) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} break case TextPlain.Mime: @@ -185,8 +187,8 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M // hash = keccak256("\x19\x00"${address}${data}). func SignTextValidator(data hexutil.Bytes) (hexutil.Bytes, string) { address := common.BytesToAddress(data[:common.AddressLength]) - message := data[common.AddressLength:len(data)-1] - hash := fmt.Sprintf("\x19\x00:%x%s", address, string(message)) + message := data[common.AddressLength:] + hash := fmt.Sprintf("\x19\x00%s%s", address, string(message)) return crypto.Keccak256(hexutil.Bytes(hash)), hash } @@ -335,20 +337,23 @@ func (typedData *TypedData) TypeHash(primaryType string) hexutil.Bytes { } func bytesValueOf(_interface interface{}) hexutil.Bytes { - bytesVal, ok := _interface.(hexutil.Bytes) + bytesValue, ok := _interface.(hexutil.Bytes) if ok { - return bytesVal + return bytesValue } switch reflect.TypeOf(_interface) { + case reflect.TypeOf(hexutil.Bytes{}): + return _interface.(hexutil.Bytes) + case reflect.TypeOf([]uint8{}): + return _interface.([]uint8) case reflect.TypeOf(string("")): return hexutil.Bytes(_interface.(string)) - break default: break } - panic(fmt.Errorf("unrecognized interface %v", _interface)) + panic(fmt.Errorf("unrecognized interface type %T", _interface)) return hexutil.Bytes{} } @@ -376,7 +381,7 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter for i := 0; i < 12; i++ { bytesValue = append(bytesValue, 0) } - for _, _byte := range encValue.(common.Address) { + for _, _byte := range common.HexToAddress(encValue.(string)) { bytesValue = append(bytesValue, _byte) } primitiveEncValue = bytesValue @@ -575,19 +580,35 @@ func (types *EIP712Types) IsValid() error { } // isStandardType checks if the given type is a EIP712 conformant type -func isStandardTypeStr(typeStr string) bool { - standardTypes := []string{ +func isStandardTypeStr(encType string) bool { + // Atomic types + for _, standardType := range []string{ TypeAddress, TypeBool, TypeBytes, - TypeInt, TypeString, + } { + if standardType == encType { + return true + } } - for _, val := range standardTypes { - if strings.HasPrefix(typeStr, val) || strings.Contains(typeStr, val) { + + // Dynamic types + for _, standardType := range []string { + TypeBytes, + TypeInt, + TypeUint, + } { + if strings.HasPrefix(encType, standardType) { return true } } + + // Reference types + if encType[len(encType)-1] == ']' { + return true + } + return false } @@ -627,15 +648,4 @@ func (domain *EIP712Domain) Map() map[string]interface{} { dataMap["salt"] = domain.Salt } return dataMap -} - -// PrintJson will be removed -func printJson(label string, output map[string]interface{}) { - jsonVal, err := json.MarshalIndent(output, "", " ") - if err != nil { - panic(err) - } - fmt.Printf("%s:", label) - fmt.Print(string(jsonVal)) - fmt.Print("\n\n") -} +} \ No newline at end of file diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index a41fbcd130e2..30396574f0a7 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -76,18 +76,18 @@ var domainStandard = EIP712Domain{ "Ether Mail", "1", big.NewInt(1), - common.HexToAddress("0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"), + "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", nil, } var dataStandard = map[string]interface{}{ "from": map[string]interface{}{ "name": "Cow", - "wallet": common.HexToAddress("0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"), + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", }, "to": map[string]interface{}{ "name": "Bob", - "wallet": common.HexToAddress("0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"), + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", }, "contents": "Hello, Bob!", } @@ -148,43 +148,42 @@ func TestSignData(t *testing.T) { 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)) + t.Errorf("Expected different hashStruct result (got %s)", 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)) + t.Errorf("Expected different hashStruct result (got %s)", 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)) + t.Errorf("Expected different encodeType result (got %s)", 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)) + t.Errorf("Expected different encodeType result (got %s)", 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)) + t.Errorf("Expected different typeHash result (got %s)", 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)) + t.Errorf("Expected different encodeData result (got %s)", dataEncoding) } } \ No newline at end of file From 05314acf9a0417bc39ece128b40a7e02a3d4c5c3 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Fri, 2 Nov 2018 10:19:25 +0100 Subject: [PATCH 19/84] Changed the property type required by signData from bytes to interface{} --- cmd/clef/audit.log | 148 +++++++++++++ cmd/clef/main.go | 4 +- signer/core/api.go | 30 +-- signer/core/auditlog.go | 18 +- signer/core/signed_data.go | 368 ++++++++++++++++++++------------ signer/core/signed_data_test.go | 28 ++- 6 files changed, 410 insertions(+), 186 deletions(-) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log index 85b2cc1f8d8d..0ce97ab5fe16 100644 --- a/cmd/clef/audit.log +++ b/cmd/clef/audit.log @@ -254,3 +254,151 @@ t=2018-10-25T20:35:43+0200 lvl=info msg=SignData api=signer type=request metad t=2018-10-25T20:41:50+0200 lvl=info msg=Configured api=signer audit log=audit.log t=2018-10-25T20:42:06+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:63054\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator t=2018-10-26T10:33:40+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T11:29:41+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T11:30:16+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T11:30:28+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:64014\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-31T11:39:42+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T11:48:29+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T11:48:37+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:64737\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-31T11:50:49+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T11:50:59+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:64820\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-31T11:51:44+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T11:52:05+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:64849\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-31T14:46:14+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T14:47:22+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:51211\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe validator:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator +t=2018-10-31T14:47:22+0100 lvl=info msg=SignData api=signer type=response data= error=nil +t=2018-10-31T15:45:57+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T15:46:31+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:51638\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[validator:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T15:46:31+0100 lvl=info msg=SignData api=signer type=response data= error="validator data not constructed correctly" +t=2018-10-31T15:46:57+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T15:47:00+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:51654\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[validator:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T15:47:00+0100 lvl=info msg=SignData api=signer type=response data= error="validator data not constructed correctly" +t=2018-10-31T16:24:12+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:24:30+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:29:29+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:51956\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[validator:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T16:35:14+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:35:19+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52038\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T16:35:19+0100 lvl=info msg=SignData api=signer type=response data= error="message is undefined" +t=2018-10-31T16:35:41+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:36:12+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52052\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T16:37:15+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:37:18+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52065\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator +t=2018-10-31T16:38:10+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:38:13+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52080\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0x1c]" content-type=text/validator +t=2018-10-31T16:38:39+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:38:42+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52097\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0x1c]" content-type=text/validator +t=2018-10-31T16:38:57+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:39:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52109\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator +t=2018-10-31T16:47:12+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:47:22+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52263\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T16:50:27+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:50:52+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:51:19+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:52:19+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:53:07+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52394\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T17:01:45+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T17:01:56+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T17:02:14+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52600\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T17:03:31+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T17:03:35+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52624\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T17:04:19+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T17:04:24+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52643\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator +t=2018-10-31T17:05:01+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T17:05:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52657\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T17:11:09+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T17:11:13+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52695\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T17:12:22+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T17:12:38+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T17:12:38+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52724\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T19:15:08+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:16:59+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:49906\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T19:28:45+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:29:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50208\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T19:29:21+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:29:32+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50219\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T19:35:37+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:35:38+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50288\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T19:36:22+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:36:29+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50310\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator +t=2018-10-31T19:36:56+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:36:59+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50328\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator +t=2018-10-31T19:40:24+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:40:26+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50369\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator +t=2018-10-31T19:41:50+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:43:03+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50469\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T19:43:58+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:44:14+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50485\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xdeadbeef]" content-type=text/validator +t=2018-10-31T19:44:30+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:44:32+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50501\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T19:47:37+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:47:56+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50552\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T19:48:10+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:48:39+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50567\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-11-01T19:16:25+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T19:16:31+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57757\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator +t=2018-11-01T21:11:47+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:13:52+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59254\",\"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-01T21:14:40+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:14:48+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59317\",\"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-01T21:16:00+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:16:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59339\",\"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-01T21:18:49+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:18:50+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59375\",\"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-01T21:26:42+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:26:55+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59485\",\"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-01T21:34:20+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:34:24+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59590\",\"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-01T21:37:05+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:37:48+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59652\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0xd090B1504cC9341BC18cE5d34D750545bb515FdC]" content-type=text/validator +t=2018-11-01T21:43:00+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:43:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59721\",\"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-01T21:44:59+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:45:01+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59759\",\"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-01T21:45:19+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:45:21+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59771\",\"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-01T21:46:31+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:46:33+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59786\",\"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-01T21:48:12+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:48:13+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59830\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain +t=2018-11-01T21:48:47+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:49:03+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:49:03+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59851\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain +t=2018-11-01T21:50:58+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:51:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59872\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain +t=2018-11-01T21:52:07+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:52:09+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59892\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain +t=2018-11-01T21:52:21+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:52:22+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59904\",\"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-01T21:53:12+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:53:14+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59919\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain +t=2018-11-01T21:53:51+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:54:08+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59957\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain +t=2018-11-01T21:54:21+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:54:26+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59971\",\"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-01T21:59:18+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:00:23+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60071\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain +t=2018-11-01T22:00:57+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:01:04+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60083\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain +t=2018-11-01T22:06:16+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:06:20+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60136\",\"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:07:41+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:07:42+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60170\",\"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:08:13+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:09:25+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60229\",\"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:11:52+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:11:53+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60257\",\"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:13:09+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:13:33+0100 lvl=info msg=EcRecover api=signer type=request metadata="{\"remote\":\"127.0.0.1:60277\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" data=cafebabe sig=d7cabcefb177b419f8c4d124cdf3487dfdf2f8e4cceb9bad5dfb8707130c9b7b394e0e7057335ce459145ff6d6b5bd52242c54e11ae5934637358930f2f6d0651b content-type=text/plain +t=2018-11-01T22:13:33+0100 lvl=info msg=EcRecover api=signer type=response address=0x66D76e6A80DC7D46d7EC1b79b15Dfa34c3C6eF21 error=nil +t=2018-11-01T22:13:44+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60277\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain +t=2018-11-01T22:15:03+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:15:24+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60301\",\"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:15:36+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:15:39+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60319\",\"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:05+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:23:20+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:23:27+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60494\",\"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:54+0100 lvl=info msg=Configured api=signer audit log=audit.log +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: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 diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 0d56149590cb..2828837592f5 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -666,8 +666,8 @@ func testExternalUI(api *core.SignerAPI) { checkErr("SignTransaction", err) _, err = api.SignData(ctx, "text/plain", common.MixedcaseAddress{}, common.Hex2Bytes("01020304")) checkErr("SignData", err) - _, err = api.SignTypedData(ctx, common.MixedcaseAddress{}, core.TypedData{}) - checkErr("SignTypedData", err) + //_, err = api.SignTypedData(ctx, common.MixedcaseAddress{}, core.TypedData{}) + //checkErr("SignTypedData", err) _, err = api.List(ctx) checkErr("List", err) _, err = api.New(ctx) diff --git a/signer/core/api.go b/signer/core/api.go index 136b3682a493..49039373725e 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -47,9 +47,9 @@ type ExternalAPI interface { // SignTransaction request to sign the specified transaction SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) // SignData - request to sign the given data (plus prefix) - SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (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(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(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) // Export - request to export an account @@ -108,30 +108,6 @@ type Metadata struct { Origin string `json:"Origin"` } -type SigFormat struct { - Mime string - ByteVersion byte -} - -var ( - TextValidator = SigFormat{ - "text/validator", - 0x00, - } - DataTyped = SigFormat{ - "data/typed", - 0x01, - } - ApplicationClique = SigFormat{ - "application/clique", - 0x02, - } - TextPlain = SigFormat{ - "text/plain", - 0x45, - } -) - // MetadataFromContext extracts Metadata from a given context.Context func MetadataFromContext(ctx context.Context) Metadata { m := Metadata{"NA", "NA", "NA", "", ""} // batman @@ -199,7 +175,7 @@ type ( SignDataRequest struct { ContentType string `json:"content_type"` Address common.MixedcaseAddress `json:"address"` - Rawdata hexutil.Bytes `json:"raw_data"` + Rawdata interface{} `json:"raw_data"` Message string `json:"message"` Hash hexutil.Bytes `json:"hash"` Meta Metadata `json:"meta"` diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go index c89c50f79ea5..b9769b16e91c 100644 --- a/signer/core/auditlog.go +++ b/signer/core/auditlog.go @@ -62,21 +62,21 @@ func (l *AuditLogger) SignTransaction(ctx context.Context, args SendTxArgs, meth return res, e } -func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { +func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error) { l.log.Info("SignData", "type", "request", "metadata", MetadataFromContext(ctx).String(), - "addr", addr.String(), "data", common.Bytes2Hex(data), "content-type", contentType) + "addr", addr.String(), "data", data, "content-type", contentType) b, e := l.api.SignData(ctx, contentType, addr, data) l.log.Info("SignData", "type", "response", "data", common.Bytes2Hex(b), "error", e) return b, e } -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(), - "addr", addr.String(), "data", data) - b, e := l.api.SignTypedData(ctx, addr, data) - l.log.Info("SignTypedData", "type", "response", "data", common.Bytes2Hex(b), "error", e) - return b, e -} +//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(), +// "addr", addr.String(), "data", data) +// b, e := l.api.SignTypedData(ctx, addr, data) +// l.log.Info("SignTypedData", "type", "response", "data", common.Bytes2Hex(b), "error", e) +// return b, e +//} func (l *AuditLogger) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { l.log.Info("EcRecover", "type", "request", "metadata", MetadataFromContext(ctx).String(), diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index eb5df4d8939b..f86845ed2f28 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -19,7 +19,6 @@ package core import ( "bytes" "context" - "encoding/json" "errors" "fmt" "math/big" @@ -39,6 +38,30 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) +type SigFormat struct { + Mime string + ByteVersion byte +} + +var ( + TextValidator = SigFormat{ + "text/validator", + 0x00, + } + DataTyped = SigFormat{ + "data/typed", + 0x01, + } + ApplicationClique = SigFormat{ + "application/clique", + 0x02, + } + TextPlain = SigFormat{ + "text/plain", + 0x45, + } +) + type ValidatorData struct { Address common.Address Message hexutil.Bytes @@ -67,7 +90,7 @@ type EIP712Domain struct { Version string `json:"version"` ChainId *big.Int `json:"chainId"` VerifyingContract string `json:"verifyingContract"` - Salt hexutil.Bytes `json:"salt"` + Salt string `json:"salt"` } const ( @@ -115,8 +138,8 @@ func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, re // depending on the content-type specified. // // Different types of validation occur. -func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { - var req, err = api.determineSignatureFormat(contentType, addr, data) +func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error) { + var req, err= api.determineSignatureFormat(contentType, addr, data) if err != nil { return nil, err } @@ -135,7 +158,7 @@ func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr com // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType // charset, ok := params["charset"] // As it is now, we accept any charset and just treat it as 'raw'. -func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (*SignDataRequest, error) { +func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.MixedcaseAddress, data interface{}) (*SignDataRequest, error) { var req *SignDataRequest mediaType, _, err := mime.ParseMediaType(contentType) if err != nil { @@ -145,26 +168,32 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M switch mediaType { case TextValidator.Mime: // Data with an intended validator - if len(data) < common.AddressLength { - return nil, errors.New("validator address and data undefined") - } - if len(data) == common.AddressLength { - return nil, errors.New("no data to sign") + validatorData, err := UnmarshalValidatorData(data) + if err != nil { + return nil, err } - sighash, msg := SignTextValidator(data) - fmt.Printf("%s", sighash) - req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + sighash, msg := SignTextValidator(validatorData) + fmt.Printf("sighash:%s", sighash) + req = &SignDataRequest{Rawdata: validatorData, Message: msg, Hash: sighash, ContentType: mediaType} break - case TextPlain.Mime: - // Sign calculates an Ethereum ECDSA signature for: - // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") - sighash, msg := SignTextPlain(data) - req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + 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: // Clique is the Ethereum PoA standard + cliqueData, err := hexutil.Decode(data.(string)) + if err != nil { + return nil, err + } header := &types.Header{} - if err := rlp.DecodeBytes(data, header); err != nil { + if err := rlp.DecodeBytes(cliqueData, header); err != nil { return nil, err } sighash, err := SignCliqueHeader(header) @@ -172,7 +201,17 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M return nil, err } msg := fmt.Sprintf("clique block %d [0x%x]", header.Number, header.Hash()) - req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + req = &SignDataRequest{Rawdata: cliqueData, Message: msg, Hash: sighash, ContentType: mediaType} + break + case TextPlain.Mime: + // Calculates an Ethereum ECDSA signature for: + // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") + plainData, err := hexutil.Decode(data.(string)) + if err != nil { + return nil, err + } + sighash, msg := SignTextPlain(plainData) + req = &SignDataRequest{Rawdata: plainData, Message: msg, Hash: sighash, ContentType: mediaType} break default: return nil, fmt.Errorf("content type '%s' not implemented for signing", contentType) @@ -181,18 +220,17 @@ 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. // // hash = keccak256("\x19\x00"${address}${data}). -func SignTextValidator(data hexutil.Bytes) (hexutil.Bytes, string) { - address := common.BytesToAddress(data[:common.AddressLength]) - message := data[common.AddressLength:] - hash := fmt.Sprintf("\x19\x00%s%s", address, string(message)) - return crypto.Keccak256(hexutil.Bytes(hash)), hash +func SignTextValidator(validatorData ValidatorData) (hexutil.Bytes, string) { + msg := fmt.Sprintf("\x19\x00%s%s", string(validatorData.Address.Bytes()), string(validatorData.Message)) + fmt.Printf("SignTextValidator:%s\n", msg) + return crypto.Keccak256([]byte(msg)), msg } -// signCliqueHeader returns the hash which is used as input for the proof-of-authority +// SignCliqueHeader returns the hash which is used as input for the proof-of-authority // signing. It is the hash of the entire header apart from the 65 byte signature // contained at the end of the extra data. // @@ -226,7 +264,7 @@ func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { return hash.Bytes(), nil } -// 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 // signing of transactions. // @@ -234,39 +272,48 @@ func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { func SignTextPlain(data hexutil.Bytes) (hexutil.Bytes, string) { // The letter `E` is \x45 in hex, retrofitting // https://github.com/ethereum/go-ethereum/pull/2940/commits - hash := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data)) - return crypto.Keccak256(hexutil.Bytes(hash)), hash + msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) + return crypto.Keccak256([]byte(msg)), msg } -// SignTypedData signs EIP-712 conformant typed data +// SignDataTyped 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) { +func SignDataTyped(typedData TypedData) (hexutil.Bytes, string) { 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 - } - buffer := bytes.Buffer{} - buffer.WriteString("\x19") - 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()) - return nil, err - } - return signature, nil + msg := fmt.Sprintf("\x19\x01%s%s", common.Bytes2Hex(domainSeparator), common.Bytes2Hex(typedDataHash)) + return crypto.Keccak256(common.Hex2Bytes(msg)), msg } -// hashStruct generates the following encoding for the given domain and message: +// 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) { +// 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 +// } +// buffer := bytes.Buffer{} +// buffer.WriteString("\x19") +// 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()) +// return nil, err +// } +// return signature, nil +//} + +// HashStruct generates the following encoding for the given domain and message: // `encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01" β€– domainSeparator β€– hashStruct(message)` func (typedData *TypedData) HashStruct(primaryType string, data EIP712Data) hexutil.Bytes { return crypto.Keccak256(typedData.EncodeData(primaryType, data)) @@ -336,28 +383,7 @@ func (typedData *TypedData) TypeHash(primaryType string) hexutil.Bytes { return crypto.Keccak256(typedData.EncodeType(primaryType)) } -func bytesValueOf(_interface interface{}) hexutil.Bytes { - bytesValue, ok := _interface.(hexutil.Bytes) - if ok { - return bytesValue - } - - switch reflect.TypeOf(_interface) { - case reflect.TypeOf(hexutil.Bytes{}): - return _interface.(hexutil.Bytes) - case reflect.TypeOf([]uint8{}): - return _interface.([]uint8) - case reflect.TypeOf(string("")): - return hexutil.Bytes(_interface.(string)) - default: - break - } - - panic(fmt.Errorf("unrecognized interface type %T", _interface)) - return hexutil.Bytes{} -} - -// encodeData generates the following encoding: +// EncodeData generates the following encoding: // `enc(value₁) β€– enc(valueβ‚‚) β€– … β€– enc(valueβ‚™)` // // each encoded member is 32-byte long @@ -458,93 +484,159 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter 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 +func bytesValueOf(_interface interface{}) hexutil.Bytes { + bytesValue, ok := _interface.(hexutil.Bytes) + if ok { + return bytesValue + } + + switch reflect.TypeOf(_interface) { + case reflect.TypeOf(hexutil.Bytes{}): + return _interface.(hexutil.Bytes) + case reflect.TypeOf([]uint8{}): + return _interface.([]uint8) + case reflect.TypeOf(string("")): + return common.Hex2Bytes(_interface.(string)) + default: + break + } + + panic(fmt.Errorf("unrecognized interface type %T", _interface)) + return hexutil.Bytes{} +} + +// EcRecover recovers the address associated with the given sig. +// Only compatible with `text/plain` func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { - mediaType, _, err := mime.ParseMediaType(contentType) + // 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 + // the address of: + // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") + // addr = ecrecover(hash, signature) + // + // Note, the signature must conform to the secp256k1 curve R, S and V values, where + // the V value must be be 27 or 28 for legacy reasons. + // + // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover + if len(sig) != 65 { + return common.Address{}, fmt.Errorf("signature must be 65 bytes long") + } + if sig[64] != 27 && sig[64] != 28 { + return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") + } + sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 + hash, _ := SignTextPlain(data) + rpk, err := crypto.SigToPub(hash, sig) if err != nil { return common.Address{}, err } - switch mediaType { - case TextPlain.Mime: - // 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 - // the address of: - // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") - // addr = ecrecover(hash, signature) - // - // Note, the signature must conform to the secp256k1 curve R, S and V values, where - // the V value must be be 27 or 28 for legacy reasons. - // - // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover - if len(sig) != 65 { - return common.Address{}, fmt.Errorf("signature must be 65 bytes long") - } - if sig[64] != 27 && sig[64] != 28 { - return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") - } - sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 - hash, _ := SignTextPlain(data) - rpk, err := crypto.SigToPub(hash, sig) - if err != nil { - return common.Address{}, err - } - return crypto.PubkeyToAddress(*rpk), nil - default: - return common.Address{}, fmt.Errorf("content type '%s' not implemented for ecRecover", contentType) - } + return crypto.PubkeyToAddress(*rpk), nil } -// UnmarshalJSON validates the input data -func (typedData *TypedData) UnmarshalJSON(data hexutil.Bytes) error { - type input struct { - Types EIP712Types `json:"types"` - PrimaryType string `json:"primaryType"` - Domain EIP712Domain `json:"domain"` - Message EIP712Data `json:"message"` +// UnmarshalValidatorData converts the bytes input to typed data +func UnmarshalValidatorData(data interface{}) (ValidatorData, error) { + raw := data.(map[string]interface{}) + + addr, ok := raw["address"].(string) + addrBytes, err := hexutil.Decode(addr) + if err != nil { + return ValidatorData{}, err + } + if !ok || len(addrBytes) == 0 { + return ValidatorData{}, errors.New("validator address is undefined") } - var raw input - if err := json.Unmarshal(data, &raw); err != nil { - return err + message, ok := raw["message"].(string) + messageBytes, err := hexutil.Decode(message) + if err != nil { + return ValidatorData{}, err } + if !ok || len(messageBytes) == 0 { + return ValidatorData{}, errors.New("message is undefined") + } + + return ValidatorData{ + Address: common.BytesToAddress(addrBytes), + Message: messageBytes, + }, nil +} - if raw.Types == nil { - return errors.New("types are undefined") +// 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 := raw.Types.IsValid(); err != nil { - return err + if err := _types.IsValid(); err != nil { + return TypedData{}, err } - typedData.Types = raw.Types - if raw.Types["EIP712Domain"] == nil { - return errors.New("domain types are undefined") + if _types["EIP712Domain"] == nil { + return TypedData{}, errors.New("domain types are undefined") } - if err := raw.Domain.IsValid(); err != nil { - return err + + domain, err := UnmarshalDomain(data) + if err != nil { + return TypedData{}, err } - typedData.Domain = raw.Domain - if len(raw.PrimaryType) == 0 { - return errors.New("primary type is undefined") + primaryType, ok := raw["primaryType"].(string) + if !ok || len(primaryType) == 0 { + return TypedData{}, errors.New("primary type is undefined") } - typedData.PrimaryType = raw.PrimaryType - if raw.Message == nil { - return errors.New("message is undefined") + message, ok := raw["message"].(EIP712Data) + if !ok || message == nil { + return TypedData{}, errors.New("message is undefined") } - typedData.Message = raw.Message + return TypedData{ + Types: _types, + PrimaryType: primaryType, + Domain: domain, + Message: message, + }, nil +} - return 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 func (typedData *TypedData) Map() map[string]interface{} { dataMap := map[string]interface{}{ - "Types": typedData.Types, - "Domain": typedData.Domain.Map(), - "PrimaryType": typedData.PrimaryType, - "Message": typedData.Message, + "types": typedData.Types, + "domain": typedData.Domain.Map(), + "primaryType": typedData.PrimaryType, + "message": typedData.Message, } return dataMap diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 30396574f0a7..060e139d3f27 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -21,6 +21,7 @@ import ( "fmt" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "math/big" "testing" ) @@ -77,10 +78,10 @@ var domainStandard = EIP712Domain{ "1", big.NewInt(1), "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", - nil, + "", } -var dataStandard = map[string]interface{}{ +var messageStandard = map[string]interface{}{ "from": map[string]interface{}{ "name": "Cow", "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", @@ -93,12 +94,19 @@ var dataStandard = map[string]interface{}{ } var typedData = TypedData{ - typesStandard, - primaryType, - domainStandard, - dataStandard, + Types: typesStandard, + PrimaryType: primaryType, + Domain: domainStandard, + Message: messageStandard, } +//var typedDataMap = map[string]interface{}{ +// "types": typesStandard, +// "primaryType": primaryType, +// "domain": domainStandard, +// "message": messageStandard, +//} + func TestSignData(t *testing.T) { api, control := setup(t) //Create two accounts @@ -113,7 +121,7 @@ func TestSignData(t *testing.T) { control <- "Y" control <- "wrongpassword" - signature, err := api.SignData(context.Background(), TextPlain.Mime, a, []byte("EHLO world")) + signature, err := api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world"))) if signature != nil { t.Errorf("Expected nil-data, got %x", signature) } @@ -121,7 +129,7 @@ func TestSignData(t *testing.T) { t.Errorf("Expected ErrLocked! %v", err) } control <- "No way" - signature, err = api.SignData(context.Background(), TextPlain.Mime, a, []byte("EHLO world")) + signature, err = api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world"))) if signature != nil { t.Errorf("Expected nil-data, got %x", signature) } @@ -131,7 +139,7 @@ func TestSignData(t *testing.T) { // text/plain control <- "Y" control <- "a_long_password" - signature, err = api.SignData(context.Background(), TextPlain.Mime, a, []byte("EHLO world")) + signature, err = api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world"))) if err != nil { t.Fatal(err) } @@ -141,7 +149,7 @@ func TestSignData(t *testing.T) { // data/typed control <- "Y" control <- "a_long_password" - signature, err = api.SignTypedData(context.Background(), a, typedData) + signature, err = api.SignData(context.Background(), DataTyped.Mime, a, typedData.Map()) if err != nil { t.Fatal(err) } From 4b893bee793edc1fe20d9e52c61db5893e82d4f9 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Fri, 2 Nov 2018 12:03:14 +0100 Subject: [PATCH 20/84] Fixed bugs in 'data/typed' signs --- cmd/clef/audit.log | 2 + signer/core/api.go | 8 +- signer/core/auditlog.go | 20 ++-- signer/core/signed_data.go | 163 +++++++------------------------- signer/core/signed_data_test.go | 22 ++--- 5 files changed, 57 insertions(+), 158 deletions(-) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log index 0ce97ab5fe16..28aa2f35965f 100644 --- a/cmd/clef/audit.log +++ b/cmd/clef/audit.log @@ -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: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-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!]}" diff --git a/signer/core/api.go b/signer/core/api.go index 49039373725e..49532f6ac43b 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -48,10 +48,10 @@ type ExternalAPI interface { SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) // SignData - request to sign the given data (plus prefix) 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(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) + // SignTypedData - request to sign the given structured data (plus prefix) + SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) // 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(ctx context.Context, addr common.Address) (json.RawMessage, error) // Import - request to import an account @@ -175,7 +175,7 @@ type ( SignDataRequest struct { ContentType string `json:"content_type"` Address common.MixedcaseAddress `json:"address"` - Rawdata interface{} `json:"raw_data"` + Rawdata interface{} `json:"raw_data"` Message string `json:"message"` Hash hexutil.Bytes `json:"hash"` Meta Metadata `json:"meta"` diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go index b9769b16e91c..578e5ddcb54a 100644 --- a/signer/core/auditlog.go +++ b/signer/core/auditlog.go @@ -70,18 +70,18 @@ func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr com return b, e } -//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(), -// "addr", addr.String(), "data", data) -// b, e := l.api.SignTypedData(ctx, addr, data) -// l.log.Info("SignTypedData", "type", "response", "data", common.Bytes2Hex(b), "error", e) -// return b, e -//} +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(), + "addr", addr.String(), "data", data) + b, e := l.api.SignTypedData(ctx, addr, data) + l.log.Info("SignTypedData", "type", "response", "data", common.Bytes2Hex(b), "error", 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(), - "data", common.Bytes2Hex(data), "sig", common.Bytes2Hex(sig), "content-type", contentType) - b, e := l.api.EcRecover(ctx, contentType, data, sig) + "data", common.Bytes2Hex(data), "sig", common.Bytes2Hex(sig)) + b, e := l.api.EcRecover(ctx, data, sig) l.log.Info("EcRecover", "type", "response", "address", b.String(), "error", e) return b, e } diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index f86845ed2f28..b8dd636a87d2 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -19,6 +19,7 @@ package core import ( "bytes" "context" + "encoding/json" "errors" "fmt" "math/big" @@ -63,8 +64,8 @@ var ( ) type ValidatorData struct { - Address common.Address - Message hexutil.Bytes + Address common.Address + Message hexutil.Bytes } type TypedData struct { @@ -86,11 +87,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 string `json:"verifyingContract"` - Salt string `json:"salt"` + Name string `json:"name"` + Version string `json:"version"` + ChainId *big.Int `json:"chainId"` + VerifyingContract string `json:"verifyingContract"` + Salt string `json:"salt"` } const ( @@ -99,7 +100,7 @@ const ( TypeBytes = "bytes" TypeInt = "int" TypeString = "string" - TypeUint = "uint" + TypeUint = "uint" ) // Sign receives a request and produces a signature @@ -139,7 +140,7 @@ func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, re // // Different types of validation occur. func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error) { - var req, err= api.determineSignatureFormat(contentType, addr, data) + var req, err = api.determineSignatureFormat(contentType, addr, data) if err != nil { return nil, err } @@ -173,19 +174,8 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M return nil, err } sighash, msg := SignTextValidator(validatorData) - fmt.Printf("sighash:%s", sighash) req = &SignDataRequest{Rawdata: validatorData, Message: msg, Hash: sighash, ContentType: mediaType} 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: // Clique is the Ethereum PoA standard 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 // with the given validator. -// // hash = keccak256("\x19\x00"${address}${data}). func SignTextValidator(validatorData ValidatorData) (hexutil.Bytes, string) { msg := fmt.Sprintf("\x19\x00%s%s", string(validatorData.Address.Bytes()), string(validatorData.Message)) @@ -267,59 +256,41 @@ func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { // 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 // signing of transactions. -// // hash = keccak256("\x19$Ethereum Signed Message:\n"${message length}${message}). func SignTextPlain(data hexutil.Bytes) (hexutil.Bytes, string) { // The letter `E` is \x45 in hex, retrofitting // 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 } -// SignDataTyped signs EIP-712 conformant typed data +// SignTypedData signs EIP-712 conformant typed data // hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}") -func SignDataTyped(typedData TypedData) (hexutil.Bytes, string) { +func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, typedData TypedData) (hexutil.Bytes, error) { 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 + _, err := json.Marshal(typedData.Map()) + if err != nil { + return nil, err + } + msg := fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)) + sighash := crypto.Keccak256([]byte(msg)) + req := &SignDataRequest{Rawdata: typedData.Map(), Message: msg, Hash: sighash, ContentType: DataTyped.Mime} + signature, err := api.Sign(ctx, addr, req) + if err != nil { + api.UI.ShowError(err.Error()) + return nil, err + } + return signature, nil } -// 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) { -// 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 -// } -// buffer := bytes.Buffer{} -// buffer.WriteString("\x19") -// 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()) -// return nil, err -// } -// return signature, nil -//} - // HashStruct generates the following encoding for the given domain and message: // `encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01" β€– domainSeparator β€– hashStruct(message)` func (typedData *TypedData) HashStruct(primaryType string, data EIP712Data) hexutil.Bytes { 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 { includes := func(arr []string, str string) bool { for _, obj := range arr { @@ -347,7 +318,7 @@ func (typedData *TypedData) Dependencies(primaryType string, found []string) []s return found } -// encodeType generates the following encoding: +// EncodeType generates the following encoding: // `name β€– "(" β€– member₁ β€– "," β€– memberβ‚‚ β€– "," β€– … β€– memberβ‚™ ")"` // // 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{}): return _interface.([]uint8) case reflect.TypeOf(string("")): - return common.Hex2Bytes(_interface.(string)) + return hexutil.Bytes(_interface.(string)) default: break } @@ -507,7 +478,7 @@ func bytesValueOf(_interface interface{}) hexutil.Bytes { // EcRecover recovers the address associated with the given sig. // 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. // // 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 } -// 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 func (typedData *TypedData) Map() map[string]interface{} { dataMap := map[string]interface{}{ @@ -686,7 +589,7 @@ func isStandardTypeStr(encType string) bool { } // Dynamic types - for _, standardType := range []string { + for _, standardType := range []string{ TypeBytes, TypeInt, TypeUint, @@ -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 { - return errors.New("domain undefined") + return errors.New("domain is undefined") } return nil @@ -740,4 +643,4 @@ func (domain *EIP712Domain) Map() map[string]interface{} { dataMap["salt"] = domain.Salt } return dataMap -} \ No newline at end of file +} diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 060e139d3f27..ac0aab0cd7ba 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -19,11 +19,12 @@ package core import ( "context" "fmt" + "math/big" + "testing" + "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "math/big" - "testing" ) var typesStandard = EIP712Types{ @@ -94,19 +95,12 @@ var messageStandard = map[string]interface{}{ } var typedData = TypedData{ - Types: typesStandard, + Types: typesStandard, PrimaryType: primaryType, - Domain: domainStandard, - Message: messageStandard, + Domain: domainStandard, + Message: messageStandard, } -//var typedDataMap = map[string]interface{}{ -// "types": typesStandard, -// "primaryType": primaryType, -// "domain": domainStandard, -// "message": messageStandard, -//} - func TestSignData(t *testing.T) { api, control := setup(t) //Create two accounts @@ -149,7 +143,7 @@ func TestSignData(t *testing.T) { // data/typed control <- "Y" 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 { t.Fatal(err) } @@ -194,4 +188,4 @@ func TestEncodeData(t *testing.T) { if dataEncoding != "0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8cd54f074a4af31b4411ff6a60c9719dbd559c221c8ac3492d9d872b041d703d1b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8" { t.Errorf("Expected different encodeData result (got %s)", dataEncoding) } -} \ No newline at end of file +} From 3adda3f3232c356b1a3b669bf11e83c197c81a34 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Fri, 2 Nov 2018 12:47:14 +0100 Subject: [PATCH 21/84] Brought legal warning back after temporarily disabling it for development --- cmd/clef/main.go | 28 ++++--- signer/core/api_layout.md | 150 ------------------------------------- signer/rules/rules_test.go | 2 +- 3 files changed, 14 insertions(+), 166 deletions(-) delete mode 100644 signer/core/api_layout.md diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 2828837592f5..d5a2dab54322 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -26,8 +26,6 @@ import ( "encoding/hex" "encoding/json" "fmt" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/rlp" "io" "io/ioutil" "math/big" @@ -42,9 +40,11 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/console" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/signer/core" "github.com/ethereum/go-ethereum/signer/rules" @@ -330,10 +330,9 @@ func initialize(c *cli.Context) error { // If using the stdioui, we can't do the 'confirm'-flow fmt.Fprintf(logOutput, legalWarning) } else { - // Temporarily disabled - //if !confirm(legalWarning) { - // return fmt.Errorf("aborted by user") - //} + if !confirm(legalWarning) { + return fmt.Errorf("aborted by user") + } } log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int(logLevelFlag.Name)), log.StreamHandler(logOutput, log.TerminalFormat(true)))) @@ -556,15 +555,14 @@ 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 - //resp, err := ui.OnInputRequired(core.UserInputRequest{ - // Title: "Master Password", - // Prompt: "Please enter the password to decrypt the master seed", - // IsPassword: true}) - //if err != nil { - // return nil, err - //} - //password = resp.Text + resp, err := ui.OnInputRequired(core.UserInputRequest{ + Title: "Master Password", + Prompt: "Please enter the password to decrypt the master seed", + IsPassword: true}) + if err != nil { + return nil, err + } + password = resp.Text } else { password = getPassPhrase("Decrypt master seed of clef", false) } diff --git a/signer/core/api_layout.md b/signer/core/api_layout.md deleted file mode 100644 index 3aae11fd07b7..000000000000 --- a/signer/core/api_layout.md +++ /dev/null @@ -1,150 +0,0 @@ -# Specs -`encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01EthereumSignedMessage\n" β€– domainSeparator β€– hashStruct(message)` -- data adheres to π•Š, a structure defined in the rigorous eip-712 -- `\x01` is needed to comply with EIP-191 -- `domainSeparator` and `hashStruct` are defined below - -## A) domainSeparator -`domainSeparator = hashStruct(eip712Domain)` -
-
-Struct named `EIP712Domain` with one or more of the below fields: - -- `string name` -- `string version` -- `uint256 chainId`, as per EIP-155 -- `address verifyingContract` -- `bytes32 salt` - -## B) hashStruct -`hashStruct(s : π•Š) = keccak256(typeHash β€– encodeData(s))` -
-`typeHash = keccak256(encodeType(typeOf(s)))` - -### i) encodeType -- `name β€– "(" β€– member₁ β€– "," β€– memberβ‚‚ β€– "," β€– … β€– memberβ‚™ ")"` -- each member is written as `type β€– " " β€– name` -- encodings cascade down and are sorted by name - -Example: `Mail(Person from,Person to,string contents)Person(string name,address wallet)` - -### ii) encodeData -- `enc(value₁) β€– enc(valueβ‚‚) β€– … β€– enc(valueβ‚™)` -- each encoded member is 32-byte long - - #### a) atomic - - - `bool` => `uint256` - - `address` => `uint160` - - `int8:int256` and `uint8:uint256` => sign-extended `uint256` in big endian order - - `bytes1:31` => `bytes32` - - #### b) dynamic - - - `bytes` => `keccak256(bytes)` - - `string` => `keccak256(string)` - - #### c) referenced - - - `array` => `keccak256(encodeData(array))` - - `struct` => `rec(keccak256(hashStruct(struct)))` - -## C) Algo -- hashStruct - - encodeType - - encodeData - - if primitive - - encode - - else - - if array - - encodeData - - else if struct - - hashStruct - - else - - break - -## D) Example -### Query -```json -{ - "jsonrpc": "2.0", - "method": "account_signTypedData", - "params": [ - "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", - { - "types": { - "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" - } - ] - }, - "primaryType": "Mail", - "domain": { - "name": "Ether Mail", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "from": { - "name": "Cow", - "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to": { - "name": "Bob", - "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents": "Hello, Bob!" - } - } - ], - "id": 1 -} -``` - -### Response -```json -{ - "id":1, - "jsonrpc": "2.0", - "result": "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c" -} -``` diff --git a/signer/rules/rules_test.go b/signer/rules/rules_test.go index 0b520a15bfcd..7f5bf71ad5a4 100644 --- a/signer/rules/rules_test.go +++ b/signer/rules/rules_test.go @@ -637,7 +637,7 @@ function ApproveSignData(r){ return } message := []byte("baz bazonk foo") - hash, msg := core.SignHash(message) + hash, msg := core.SignTextPlain(message) raw := hexutil.Bytes(message) addr, _ := mixAddr("0x694267f14675d7e1b9494fd8d72fefe1755710fa") From cca0a9628f8c8fe80df7f00dc0f4d4569ab73514 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Mon, 5 Nov 2018 17:54:58 +0200 Subject: [PATCH 22/84] Added example RPC calls for account_signData and account_signTypedData --- cmd/clef/README.md | 126 +++++++++++++++++++++++++++++++---- cmd/clef/extapi_changelog.md | 2 +- signer/core/signed_data.go | 17 +---- 3 files changed, 117 insertions(+), 28 deletions(-) diff --git a/cmd/clef/README.md b/cmd/clef/README.md index 8fcaae7cb590..205b183e5000 100644 --- a/cmd/clef/README.md +++ b/cmd/clef/README.md @@ -189,7 +189,9 @@ None "method": "account_new", "params": [] } - +``` +Response +``` { "id": 0, "jsonrpc": "2.0", @@ -222,7 +224,9 @@ None "jsonrpc": "2.0", "method": "account_list" } - +``` +Response +``` { "id": 1, "jsonrpc": "2.0", @@ -285,8 +289,8 @@ Response ```json { + "id": 2, "jsonrpc": "2.0", - "id": 67, "error": { "code": -32000, "message": "Request denied" @@ -298,6 +302,7 @@ Response ```json { + "id": 67, "jsonrpc": "2.0", "method": "account_signTransaction", "params": [ @@ -311,8 +316,7 @@ Response "data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012" }, "safeSend(address)" - ], - "id": 67 + ] } ``` Response @@ -346,16 +350,18 @@ Bash example: {"jsonrpc":"2.0","id":67,"result":{"raw":"0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","tx":{"nonce":"0x0","gasPrice":"0x1","gas":"0x333","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0","value":"0x0","input":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012","v":"0x26","r":"0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e","s":"0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","hash":"0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e"}}} ``` - ### account_signData #### Sign data Signs a chunk of data and returns the calculated signature. #### Arguments - - content type [string]: type of data to sign + - content type [string]: type of signed data + - `text/validator`: hex data with custom validator defined in a contract + - `application/clique`: [clique](https://github.com/ethereum/EIPs/issues/225) headers + - `text/plain`: simple hex data validated by `account_ecRecover` - account [address]: account to sign with - - data [data]: data to sign + - data [object]: data to sign #### Result - calculated signature [data] @@ -377,19 +383,115 @@ Response ```json { - "id": 3, "jsonrpc": "2.0", + "id": 3, "result": "0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c" } ``` +### account_signTypedData + +#### Sign data + Signs a chunk of structured data conformant to [EIP712]([EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md)) and returns the calculated signature. + +#### Arguments + - account [address]: account to sign with + - data [object]: data to sign + +#### Result + - calculated signature [data] + +#### Sample call +```json +{ + "id": 68, + "jsonrpc": "2.0", + "method": "account_signTypedData", + "params": [ + "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", + { + "types": { + "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" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": 1, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } + } + ] +} +``` +Response + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c" +} +``` + ### account_ecRecover #### Sign data - Derive the address from the account that was used to sign data from the data and signature. + Derive the address from the account that was used to sign data with content type `text/plain` and the signature. #### Arguments - - content type [string]: type of signed data - data [data]: data that was signed - signature [data]: the signature to verify @@ -461,7 +563,7 @@ Response }, "id": "09bccb61-b8d3-4e93-bf4f-205a8194f0b9", "version": 3 - }, + } ] } ``` diff --git a/cmd/clef/extapi_changelog.md b/cmd/clef/extapi_changelog.md index a9ab42e5b521..7adc33960590 100644 --- a/cmd/clef/extapi_changelog.md +++ b/cmd/clef/extapi_changelog.md @@ -8,7 +8,7 @@ The addition of `contentType` makes it possible to use the method for different * signing data with an intended validator (not yet implemented) * signing clique headers, * signing plain personal messages, -* The external method `account_signTypedData` [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md) and makes it possible to sign typed data. +* The external method `account_signTypedData` implements [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md) and makes it possible to sign typed data. #### 4.0.0 diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index b8dd636a87d2..532d2c99a5c2 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -175,7 +175,6 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M } sighash, msg := SignTextValidator(validatorData) req = &SignDataRequest{Rawdata: validatorData, Message: msg, Hash: sighash, ContentType: mediaType} - break case ApplicationClique.Mime: // Clique is the Ethereum PoA standard cliqueData, err := hexutil.Decode(data.(string)) @@ -192,7 +191,6 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M } msg := fmt.Sprintf("clique block %d [0x%x]", header.Number, header.Hash()) req = &SignDataRequest{Rawdata: cliqueData, Message: msg, Hash: sighash, ContentType: mediaType} - break case TextPlain.Mime: // Calculates an Ethereum ECDSA signature for: // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") @@ -202,7 +200,6 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M } sighash, msg := SignTextPlain(plainData) req = &SignDataRequest{Rawdata: plainData, Message: msg, Hash: sighash, ContentType: mediaType} - break default: return nil, fmt.Errorf("content type '%s' not implemented for signing", contentType) } @@ -382,7 +379,6 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter bytesValue = append(bytesValue, _byte) } primitiveEncValue = bytesValue - break case "bool": primitiveEncType = "uint256" var int64Val int64 @@ -390,11 +386,9 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter 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") @@ -404,15 +398,12 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter for i := 0; i < 32-size; i++ { bytesValue = append(bytesValue, 0) } - for _, _byte := range encValue.(hexutil.Bytes) { - bytesValue = append(bytesValue, _byte) - } + bytesValue = append(bytesValue, encValue.(hexutil.Bytes)...) primitiveEncValue = bytesValue } else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { primitiveEncType = "uint256" primitiveEncValue = abi.U256(encValue.(*big.Int)) } - break } return primitiveEncType, primitiveEncValue } @@ -600,11 +591,7 @@ func isStandardTypeStr(encType string) bool { } // Reference types - if encType[len(encType)-1] == ']' { - return true - } - - return false + return encType[len(encType)-1] == ']' } // IsValid checks if the given domain is valid, i.e. contains at least From 51b1addf3d5762df9ed18b9cb037e52769729dfa Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Thu, 8 Nov 2018 23:08:41 +0200 Subject: [PATCH 23/84] Polished and fixed PR --- accounts/accounts.go | 2 +- cmd/clef/audit.log | 406 ----------------------------------- cmd/clef/intapi_changelog.md | 4 + signer/core/signed_data.go | 11 +- 4 files changed, 10 insertions(+), 413 deletions(-) delete mode 100644 cmd/clef/audit.log diff --git a/accounts/accounts.go b/accounts/accounts.go index a0675ab7a609..cb1eae281587 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -109,7 +109,7 @@ type Wallet interface { // a password to decrypt the account, or a PIN code to verify the transaction), // an AuthNeededError instance will be returned, containing infos for the user // about which fields or actions are needed. The user may retry by providing - // the needed details via SignHashWithPassphraseSignTxWithPassphrase, or by other means (e.g. unlock + // the needed details via SignTxWithPassphrase, or by other means (e.g. unlock // the account in a keystore). SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log deleted file mode 100644 index 28aa2f35965f..000000000000 --- a/cmd/clef/audit.log +++ /dev/null @@ -1,406 +0,0 @@ -t=2018-10-13T05:03:16-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-13T05:04:21-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-13T05:04:35-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61564\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr=data LOG15_ERROR= LOG15_ERROR="Normalized odd number of arguments by adding nil" -t=2018-10-13T05:04:35-0700 lvl=info msg=SignStructuredData api=signer type=response data= error=nil -t=2018-10-13T05:07:31-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-13T05:35:59-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-13T05:37:45-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-13T05:42:26-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-13T05:43:13-0700 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61669\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[type:string name:version] 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:{neg:false abs:[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-13T05:43:13-0700 lvl=info msg=SignTypedData api=signer type=response data= error=nil -t=2018-10-13T05:43:50-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-13T05:44:06-0700 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61680\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Mail:[map[name:from type:Person] map[type:Person name:to] 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]] Person:[map[type:string name:name] map[name:wallet type:address]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:{neg:false abs:[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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" -t=2018-10-13T05:44:06-0700 lvl=info msg=SignTypedData api=signer type=response data= error=nil -t=2018-10-13T05:46:53-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-13T05:46:58-0700 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61694\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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-13T05:46:58-0700 lvl=info msg=SignTypedData api=signer type=response data= error=nil -t=2018-10-14T21:00:04+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T21:00:06+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56799\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-14T21:00:06+0100 lvl=info msg=SignTypedData api=signer type=response data= error="Couldn't decode types of EIP712Domain" -t=2018-10-14T21:00:50+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T21:01:02+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56810\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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:{neg:false abs:[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-14T21:01:02+0100 lvl=info msg=SignTypedData api=signer type=response data= error="Couldn't decode types of EIP712Domain" -t=2018-10-14T21:01:39+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T21:01:53+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56823\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" -t=2018-10-14T21:01:53+0100 lvl=info msg=SignTypedData api=signer type=response data= error="Couldn't decode types of EIP712Domain" -t=2018-10-14T21:11:30+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T21:11:33+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56899\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] map[name:verifyingContract type:address]] Person:[map[type:string name:name] 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:{neg:false abs:[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-14T21:11:33+0100 lvl=info msg=SignTypedData api=signer type=response data= error="Couldn't decode types of EIP712Domain" -t=2018-10-14T21:15:28+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T21:15:34+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56968\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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-14T21:15:34+0100 lvl=info msg=SignTypedData api=signer type=response data= error="Couldn't decode types of EIP712Domain" -t=2018-10-14T21:17:53+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T21:17:57+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56991\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:Person name:to] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:{neg:false abs:[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-14T21:17:57+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-14T21:18:32+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T21:18:36+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57006\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:Person name:from] map[name:to type:Person] map[type:string name:contents]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:{neg:false abs:[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-14T21:18:36+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-14T21:18:59+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T21:19:00+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57017\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] EIP712Domain:[map[name:name type:string] map[type:string name:version] map[name:chainId type:uint256] map[name:verifyingContract type:address]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:{neg:false abs:[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-14T21:19:00+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-14T21:30:19+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T21:30:29+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57188\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-14T21:30:29+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-14T21:51:40+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T21:51:42+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57392\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] EIP712Domain:[map[name:name type:string] map[type:string name:version] 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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-14T21:51:42+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-14T22:16:07+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T22:16:14+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57602\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]]}" -t=2018-10-14T22:16:14+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-14T22:18:31+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T22:18:35+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57653\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" -t=2018-10-14T22:18:35+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-14T22:20:47+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T22:20:50+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57669\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]] EIP712Domain:[map[name:name type:string] map[type:string name:version] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet 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-14T22:20:50+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-15T03:11:09+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-15T03:11:17+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50605\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-15T03:11:17+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-15T03:12:01+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-15T03:12:04+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50618\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" -t=2018-10-15T03:12:04+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-15T03:14:48+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-15T03:15:25+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50635\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-15T03:15:25+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-20T15:17:32+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:17:53+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:22:24+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:24:48+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:24:54+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56168\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[] PrimaryType: Domain:{Name: Version: ChainId: VerifyingContract:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Salt:0x} Message:map[]}" -t=2018-10-20T15:24:54+0100 lvl=info msg=SignTypedData api=signer type=response data= error="primary type undefined" -t=2018-10-20T15:25:28+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:27:12+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:27:59+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:28:02+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56262\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[] PrimaryType: Domain:{Name: Version: ChainId: VerifyingContract:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Salt:0x} Message:map[]}" -t=2018-10-20T15:28:02+0100 lvl=info msg=SignTypedData api=signer type=response data= error="primary type undefined" -t=2018-10-20T15:28:15+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56262\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[] PrimaryType: Domain:{Name: Version: ChainId: VerifyingContract:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Salt:0x} Message:map[]}" -t=2018-10-20T15:28:15+0100 lvl=info msg=SignTypedData api=signer type=response data= error="primary type undefined" -t=2018-10-20T15:29:47+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:29:52+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56281\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[] PrimaryType: Domain:{Name: Version: ChainId: VerifyingContract:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Salt:0x} Message:map[]}" -t=2018-10-20T15:32:20+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:32:46+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56456\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:Person name:from] map[type:Person name:to] 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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-20T15:34:35+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:35:24+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56476\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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:[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-20T15:37:05+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:37:21+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56561\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:Person name:to] 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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" -t=2018-10-20T15:38:27+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:38:36+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56583\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[type:string name:version] 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[to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" -t=2018-10-20T15:40:49+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:40:54+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56604\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T15:41:20+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:41:38+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56681\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[type:string name:name] map[type:string name:version] 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:[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-20T15:49:10+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:49:20+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56804\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:string name:contents]]] 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-20T15:51:01+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:51:31+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56859\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Mail:[map[type:Person name:from] 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]] Person:[map[name:name type:string] map[name:wallet 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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-20T15:51:59+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:52:23+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56905\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T16:05:44+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:05:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58137\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-20T16:06:22+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:06:25+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58154\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[type:string name:name] map[type:string name:version] 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[type:Person name:to] 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-20T16:06:39+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:06:43+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58213\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[type:string name:name] map[name:version type:string] map[name:chainId type:uint256] map[type:address name:verifyingContract]] 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:[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-20T16:06:55+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:07:53+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58301\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] Person:[map[type:string name:name] map[name:wallet 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[contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]]}" -t=2018-10-20T16:10:03+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:10:05+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58446\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:string name:contents]] 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:[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-20T16:10:39+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:10:43+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58497\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T16:12:31+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:12:37+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58538\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[type:string name:version] 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:[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-20T16:12:55+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58538\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" -t=2018-10-20T16:14:08+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:14:11+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58563\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[type:string name:version] map[name:chainId type:uint256] map[type:address name:verifyingContract]] 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:[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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice]]}" -t=2018-10-20T16:17:07+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:17:13+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58619\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T16:18:51+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:19:01+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:20:03+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58718\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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:[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-20T16:24:34+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:25:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58919\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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-20T16:30:18+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:30:40+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58983\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T16:30:59+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:31:01+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59036\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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-20T16:31:36+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:32:25+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59121\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice]]}" -t=2018-10-20T19:01:23+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T19:01:51+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59614\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] Person:[map[name:name type:string] map[name:wallet 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-20T19:02:26+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T19:02:41+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59722\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T19:06:00+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T19:07:47+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60455\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T19:08:16+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60455\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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[contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]]}" -t=2018-10-20T19:09:46+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60587\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T19:11:12+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T19:11:21+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60710\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" -t=2018-10-20T19:11:35+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60710\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] 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-20T19:11:51+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T19:11:53+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60768\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] 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-20T19:12:11+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T19:12:30+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60809\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" -t=2018-10-20T19:13:53+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T19:14:04+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60898\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T19:14:32+0100 lvl=info msg=SignTypedData api=signer type=response data=686365d6fa3d2a98d7c67101a8acf295c644a8de80b8ecc89cff276355897f6d error=nil -t=2018-10-20T19:36:35+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T19:36:40+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61953\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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-20T19:36:40+0100 lvl=info msg=SignTypedData api=signer type=response data=7951623617a41fec181fafc3555c0a494d01dce0408b6596964f5f50acba239e error=nil -t=2018-10-21T14:58:59+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T15:00:47+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T15:00:47+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52242\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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-21T15:05:18+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T15:07:18+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T15:07:20+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52313\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] Person:[map[name:name type:string] map[type:address name:wallet]]] 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-21T15:07:45+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T15:07:48+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52326\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-21T15:08:15+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T15:08:18+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52339\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:uint256 name:chainId] 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-21T15:08:46+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T15:08:51+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52351\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" -t=2018-10-21T15:09:58+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52360\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-21T15:10:10+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T15:10:23+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52371\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:string name:contents]]] 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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-21T15:11:27+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52373\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=deadbeef content-type=text/plain -t=2018-10-21T15:47:19+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T15:54:17+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:53039\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"Apache-HttpClient/4.5.5 (Java/1.8.0_152-release)\",\"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:[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-21T18:53:59+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T18:54:53+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T18:54:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54450\",\"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:[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-21T19:00:29+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:00:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54496\",\"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[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:[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-21T19:01:35+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:01:45+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54509\",\"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[type:Person name:from] map[type:Person name:to] map[type:string name:contents]] 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-21T19:06:53+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:07:33+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:07:56+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54595\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66D76e6A80DC7D46d7EC1b79b15Dfa34c3C6eF21 [chksum ok]" data="{Types:map[Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]] EIP712Domain:[map[name:name type:string] map[type:string name:version] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet 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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-21T19:09:56+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:09:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54620\",\"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[type:address name:verifyingContract]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[type:Person name:to] map[type:string name:contents]]] 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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-21T19:10:23+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:10:46+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54635\",\"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:[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-21T19:16:04+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:16:11+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54666\",\"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[type:uint256 name:chainId] 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[from:map[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" -t=2018-10-21T19:19:07+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:19:12+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54690\",\"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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" -t=2018-10-21T19:20:49+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:20:54+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54713\",\"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:[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-21T19:53:52+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:54:07+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54849\",\"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:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" -t=2018-10-21T19:55:14+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:55:19+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54863\",\"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[type:string name:version] map[name:chainId type:uint256] map[type:address name:verifyingContract]] 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:[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-21T19:56:13+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:56:18+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54874\",\"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[type:uint256 name:chainId] map[type:address name:verifyingContract]] 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:[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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-21T19:57:02+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:57:41+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:57:43+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54896\",\"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[type:string name:version] 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:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-21T19:58:03+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:58:04+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54908\",\"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[type:uint256 name:chainId] map[type:address name:verifyingContract]] 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:[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-21T19:58:39+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:58:44+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54920\",\"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:[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-21T20:00:09+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T20:00:34+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54933\",\"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[type:address name:verifyingContract]] 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:[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:03:51+0100 lvl=info msg=Configured api=signer audit log=audit.log -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!]}" -t=2018-10-25T13:47:17+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T13:47:54+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T13:50:09+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50039\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T13:51:00+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T13:51:07+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:51792\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T13:57:27+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T13:57:30+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:62734\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T14:01:29+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T14:01:30+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:53871\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608adeadbeef content-type=text/validator -t=2018-10-25T14:02:00+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T14:02:10+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:55131\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608adeadbeef content-type=text/validator -t=2018-10-25T14:13:15+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T14:13:16+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58361\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T14:13:53+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T14:14:04+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59928\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T14:14:39+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T14:14:43+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61106\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T14:21:56+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T14:22:59+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59342\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=60deadbeef content-type=text/validator -t=2018-10-25T14:22:59+0200 lvl=info msg=SignData api=signer type=response data= error="validator address and data undefined" -t=2018-10-25T14:23:05+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59342\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T14:23:31+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T14:23:55+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61180\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T20:12:47+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T20:13:00+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59607\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T20:34:51+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T20:34:53+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:62187\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T20:35:38+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T20:35:43+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:62300\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T20:41:50+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T20:42:06+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:63054\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-26T10:33:40+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T11:29:41+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T11:30:16+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T11:30:28+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:64014\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-31T11:39:42+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T11:48:29+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T11:48:37+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:64737\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-31T11:50:49+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T11:50:59+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:64820\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-31T11:51:44+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T11:52:05+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:64849\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-31T14:46:14+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T14:47:22+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:51211\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe validator:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator -t=2018-10-31T14:47:22+0100 lvl=info msg=SignData api=signer type=response data= error=nil -t=2018-10-31T15:45:57+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T15:46:31+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:51638\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[validator:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T15:46:31+0100 lvl=info msg=SignData api=signer type=response data= error="validator data not constructed correctly" -t=2018-10-31T15:46:57+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T15:47:00+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:51654\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[validator:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T15:47:00+0100 lvl=info msg=SignData api=signer type=response data= error="validator data not constructed correctly" -t=2018-10-31T16:24:12+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:24:30+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:29:29+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:51956\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[validator:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T16:35:14+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:35:19+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52038\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T16:35:19+0100 lvl=info msg=SignData api=signer type=response data= error="message is undefined" -t=2018-10-31T16:35:41+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:36:12+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52052\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T16:37:15+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:37:18+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52065\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator -t=2018-10-31T16:38:10+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:38:13+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52080\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0x1c]" content-type=text/validator -t=2018-10-31T16:38:39+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:38:42+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52097\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0x1c]" content-type=text/validator -t=2018-10-31T16:38:57+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:39:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52109\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator -t=2018-10-31T16:47:12+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:47:22+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52263\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T16:50:27+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:50:52+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:51:19+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:52:19+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:53:07+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52394\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T17:01:45+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T17:01:56+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T17:02:14+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52600\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T17:03:31+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T17:03:35+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52624\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T17:04:19+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T17:04:24+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52643\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator -t=2018-10-31T17:05:01+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T17:05:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52657\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T17:11:09+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T17:11:13+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52695\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T17:12:22+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T17:12:38+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T17:12:38+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52724\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T19:15:08+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:16:59+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:49906\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T19:28:45+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:29:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50208\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T19:29:21+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:29:32+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50219\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T19:35:37+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:35:38+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50288\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T19:36:22+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:36:29+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50310\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator -t=2018-10-31T19:36:56+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:36:59+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50328\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator -t=2018-10-31T19:40:24+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:40:26+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50369\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator -t=2018-10-31T19:41:50+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:43:03+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50469\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T19:43:58+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:44:14+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50485\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xdeadbeef]" content-type=text/validator -t=2018-10-31T19:44:30+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:44:32+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50501\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T19:47:37+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:47:56+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50552\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T19:48:10+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:48:39+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50567\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-11-01T19:16:25+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T19:16:31+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57757\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator -t=2018-11-01T21:11:47+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:13:52+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59254\",\"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-01T21:14:40+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:14:48+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59317\",\"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-01T21:16:00+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:16:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59339\",\"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-01T21:18:49+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:18:50+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59375\",\"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-01T21:26:42+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:26:55+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59485\",\"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-01T21:34:20+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:34:24+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59590\",\"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-01T21:37:05+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:37:48+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59652\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0xd090B1504cC9341BC18cE5d34D750545bb515FdC]" content-type=text/validator -t=2018-11-01T21:43:00+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:43:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59721\",\"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-01T21:44:59+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:45:01+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59759\",\"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-01T21:45:19+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:45:21+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59771\",\"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-01T21:46:31+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:46:33+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59786\",\"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-01T21:48:12+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:48:13+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59830\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain -t=2018-11-01T21:48:47+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:49:03+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:49:03+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59851\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain -t=2018-11-01T21:50:58+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:51:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59872\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain -t=2018-11-01T21:52:07+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:52:09+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59892\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain -t=2018-11-01T21:52:21+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:52:22+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59904\",\"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-01T21:53:12+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:53:14+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59919\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain -t=2018-11-01T21:53:51+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:54:08+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59957\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain -t=2018-11-01T21:54:21+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:54:26+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59971\",\"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-01T21:59:18+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:00:23+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60071\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain -t=2018-11-01T22:00:57+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:01:04+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60083\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain -t=2018-11-01T22:06:16+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:06:20+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60136\",\"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:07:41+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:07:42+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60170\",\"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:08:13+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:09:25+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60229\",\"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:11:52+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:11:53+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60257\",\"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:13:09+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:13:33+0100 lvl=info msg=EcRecover api=signer type=request metadata="{\"remote\":\"127.0.0.1:60277\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" data=cafebabe sig=d7cabcefb177b419f8c4d124cdf3487dfdf2f8e4cceb9bad5dfb8707130c9b7b394e0e7057335ce459145ff6d6b5bd52242c54e11ae5934637358930f2f6d0651b content-type=text/plain -t=2018-11-01T22:13:33+0100 lvl=info msg=EcRecover api=signer type=response address=0x66D76e6A80DC7D46d7EC1b79b15Dfa34c3C6eF21 error=nil -t=2018-11-01T22:13:44+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60277\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain -t=2018-11-01T22:15:03+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:15:24+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60301\",\"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:15:36+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:15:39+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60319\",\"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:05+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:23:20+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:23:27+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60494\",\"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:54+0100 lvl=info msg=Configured api=signer audit log=audit.log -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: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!]}" diff --git a/cmd/clef/intapi_changelog.md b/cmd/clef/intapi_changelog.md index 92a39a268fd3..6388af73031a 100644 --- a/cmd/clef/intapi_changelog.md +++ b/cmd/clef/intapi_changelog.md @@ -1,5 +1,9 @@ ### Changelog for internal API (ui-api) +### 3.1.0 + +* Add `ContentType string` to `SignDataRequest` to accommodate the latest EIP-191 and EIP-712 implementations. + ### 3.0.0 * Make use of `OnInputRequired(info UserInputRequest)` for obtaining master password during startup diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 532d2c99a5c2..5a736481cf5e 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -174,7 +174,7 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M return nil, err } sighash, msg := SignTextValidator(validatorData) - req = &SignDataRequest{Rawdata: validatorData, Message: msg, Hash: sighash, ContentType: mediaType} + req = &SignDataRequest{ContentType: mediaType, Rawdata: validatorData, Message: msg, Hash: sighash} case ApplicationClique.Mime: // Clique is the Ethereum PoA standard cliqueData, err := hexutil.Decode(data.(string)) @@ -190,7 +190,7 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M return nil, err } msg := fmt.Sprintf("clique block %d [0x%x]", header.Number, header.Hash()) - req = &SignDataRequest{Rawdata: cliqueData, Message: msg, Hash: sighash, ContentType: mediaType} + req = &SignDataRequest{ContentType: mediaType, Rawdata: cliqueData, Message: msg, Hash: sighash} case TextPlain.Mime: // Calculates an Ethereum ECDSA signature for: // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") @@ -199,7 +199,7 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M return nil, err } sighash, msg := SignTextPlain(plainData) - req = &SignDataRequest{Rawdata: plainData, Message: msg, Hash: sighash, ContentType: mediaType} + req = &SignDataRequest{ContentType: mediaType, Rawdata: plainData, Message: msg, Hash: sighash} default: return nil, fmt.Errorf("content type '%s' not implemented for signing", contentType) } @@ -272,7 +272,7 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd } msg := fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)) sighash := crypto.Keccak256([]byte(msg)) - req := &SignDataRequest{Rawdata: typedData.Map(), Message: msg, Hash: sighash, ContentType: DataTyped.Mime} + req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: typedData.Map(), Message: msg, Hash: sighash} signature, err := api.Sign(ctx, addr, req) if err != nil { api.UI.ShowError(err.Error()) @@ -281,8 +281,7 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd return signature, nil } -// HashStruct generates the following encoding for the given domain and message: -// `encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01" β€– domainSeparator β€– hashStruct(message)` +// HashStruct generates a keccak256 hash of the encoding of the provided data func (typedData *TypedData) HashStruct(primaryType string, data EIP712Data) hexutil.Bytes { return crypto.Keccak256(typedData.EncodeData(primaryType, data)) } From f8348a074c3af2c8aa0f078ad2df167b96a68177 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Fri, 9 Nov 2018 20:35:05 +0200 Subject: [PATCH 24/84] Solved malformed data panics and also wrote tests --- signer/core/cliui.go | 2 +- signer/core/signed_data.go | 141 ++++++++++++----- signer/core/signed_data_test.go | 266 +++++++++++++++++++++++++++++++- 3 files changed, 369 insertions(+), 40 deletions(-) diff --git a/signer/core/cliui.go b/signer/core/cliui.go index 7fefaabd7589..1e5927b242b0 100644 --- a/signer/core/cliui.go +++ b/signer/core/cliui.go @@ -165,7 +165,7 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResp fmt.Printf("-------- Sign data request--------------\n") fmt.Printf("Account: %s\n", request.Address.String()) fmt.Printf("message: \n%q\n", request.Message) - fmt.Printf("raw data: \n%v\n", request.Rawdata) + fmt.Printf("raw data: \n%v\n", request.Rawdata) fmt.Printf("message hash: %v\n", request.Hash) fmt.Printf("-------------------------------------------\n") showMetadata(request.Meta) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 5a736481cf5e..95454acfdc23 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -19,7 +19,6 @@ package core import ( "bytes" "context" - "encoding/json" "errors" "fmt" "math/big" @@ -264,9 +263,14 @@ func SignTextPlain(data hexutil.Bytes) (hexutil.Bytes, string) { // 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) { - domainSeparator := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) - typedDataHash := typedData.HashStruct(typedData.PrimaryType, typedData.Message) - _, err := json.Marshal(typedData.Map()) + if err := typedData.IsValid(); err != nil { + return nil, err + } + domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) + if err != nil { + return nil, err + } + typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) if err != nil { return nil, err } @@ -282,8 +286,12 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd } // HashStruct generates a keccak256 hash of the encoding of the provided data -func (typedData *TypedData) HashStruct(primaryType string, data EIP712Data) hexutil.Bytes { - return crypto.Keccak256(typedData.EncodeData(primaryType, data)) +func (typedData *TypedData) HashStruct(primaryType string, data EIP712Data) (hexutil.Bytes, error) { + encodedData, err := typedData.EncodeData(primaryType, data) + if err != nil { + return nil, err + } + return crypto.Keccak256(encodedData), nil } // Dependencies returns an array of custom types ordered by their hierarchical reference tree @@ -354,7 +362,7 @@ func (typedData *TypedData) TypeHash(primaryType string) hexutil.Bytes { // `enc(value₁) β€– enc(valueβ‚‚) β€– … β€– enc(valueβ‚™)` // // each encoded member is 32-byte long -func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}) hexutil.Bytes { +func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}) (hexutil.Bytes, error) { encTypes := []string{} encValues := []interface{}{} @@ -362,8 +370,13 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter encTypes = append(encTypes, "bytes32") encValues = append(encValues, typedData.TypeHash(primaryType)) + // Generate error for a mismatch between the provided type and data + dataMismatchError := func(encType string, encValue interface{}) error { + return fmt.Errorf("provided data '%v' doesn't match type '%s'", encValue, encType) + } + // Handle primitive values - handlePrimitiveValue := func(encType string, encValue interface{}) (string, interface{}) { + handlePrimitiveValue := func(encType string, encValue interface{}) (string, interface{}, error) { var primitiveEncType string var primitiveEncValue interface{} @@ -374,20 +387,32 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter for i := 0; i < 12; i++ { bytesValue = append(bytesValue, 0) } - for _, _byte := range common.HexToAddress(encValue.(string)) { + stringValue, ok := encValue.(string) + if !ok || !common.IsHexAddress(stringValue) { + return "", nil, dataMismatchError(encType, encValue) + } + for _, _byte := range common.HexToAddress(stringValue) { bytesValue = append(bytesValue, _byte) } primitiveEncValue = bytesValue case "bool": primitiveEncType = "uint256" var int64Val int64 - if encValue.(bool) { + boolValue, ok := encValue.(bool) + if !ok { + return "", nil, dataMismatchError(encType, encValue) + } + if boolValue { int64Val = 1 } primitiveEncValue = abi.U256(big.NewInt(int64Val)) case "bytes", "string": primitiveEncType = "bytes32" - primitiveEncValue = crypto.Keccak256(bytesValueOf(encValue)) + bytesValue, err := bytesValueOf(encValue) + if err != nil { + return "", nil, dataMismatchError(encType, encValue) + } + primitiveEncValue = crypto.Keccak256(bytesValue) default: if strings.HasPrefix(encType, "bytes") { encTypes = append(encTypes, "bytes32") @@ -397,14 +422,21 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter for i := 0; i < 32-size; i++ { bytesValue = append(bytesValue, 0) } + if _, ok := encValue.(hexutil.Bytes); !ok { + return "", nil, dataMismatchError(encType, encValue) + } bytesValue = append(bytesValue, encValue.(hexutil.Bytes)...) primitiveEncValue = bytesValue } else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { primitiveEncType = "uint256" - primitiveEncValue = abi.U256(encValue.(*big.Int)) + bigIntValue, ok := encValue.(*big.Int) + if !ok { + return "", nil, dataMismatchError(encType, encValue) + } + primitiveEncValue = abi.U256(bigIntValue) } } - return primitiveEncType, primitiveEncValue + return primitiveEncType, primitiveEncValue, nil } // Add field contents. Structs and arrays have special handlings. @@ -414,24 +446,49 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter 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) + mapValue, ok := item.(map[string]interface{}) + if !ok { + return nil, dataMismatchError(parsedType, item) + } + encodedData, err := typedData.EncodeData(parsedType, mapValue) + if err != nil { + return nil, err + } + arrayBuffer.Write(encodedData) } else { - _, encValue := handlePrimitiveValue(encType, encValue) - arrayBuffer.Write(bytesValueOf(encValue)) + _, encValue, err := handlePrimitiveValue(encType, encValue) + if err != nil { + return nil, err + } + bytesValue, err := bytesValueOf(encValue) + if err != nil { + return nil, err + } + arrayBuffer.Write(bytesValue) } } 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)) + mapValue, ok := encValue.(map[string]interface{}) + if !ok { + return nil, dataMismatchError(encType, encValue) + } + encodedData, err := typedData.EncodeData(field["type"], mapValue) + if err != nil { + return nil, err + } + encValue = crypto.Keccak256(encodedData) encValues = append(encValues, encValue) } else { - primitiveEncType, primitiveEncValue := handlePrimitiveValue(encType, encValue) + primitiveEncType, primitiveEncValue, err := handlePrimitiveValue(encType, encValue) + if err != nil { + return nil, err + } encTypes = append(encTypes, primitiveEncType) encValues = append(encValues, primitiveEncValue) } @@ -439,31 +496,34 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter buffer := bytes.Buffer{} for _, encValue := range encValues { - buffer.Write(bytesValueOf(encValue)) + bytesValue, err := bytesValueOf(encValue) + if err != nil { + return nil, err + } + buffer.Write(bytesValue) } - return buffer.Bytes() // https://github.com/ethereumjs/ethereumjs-abi/blob/master/lib/index.js#L336 + return buffer.Bytes(), nil // https://github.com/ethereumjs/ethereumjs-abi/blob/master/lib/index.js#L336 } -func bytesValueOf(_interface interface{}) hexutil.Bytes { +func bytesValueOf(_interface interface{}) (hexutil.Bytes, error) { bytesValue, ok := _interface.(hexutil.Bytes) if ok { - return bytesValue + return bytesValue, nil } switch reflect.TypeOf(_interface) { case reflect.TypeOf(hexutil.Bytes{}): - return _interface.(hexutil.Bytes) + return _interface.(hexutil.Bytes), nil case reflect.TypeOf([]uint8{}): - return _interface.([]uint8) + return _interface.([]uint8), nil case reflect.TypeOf(string("")): - return hexutil.Bytes(_interface.(string)) + return hexutil.Bytes(_interface.(string)), nil default: break } - panic(fmt.Errorf("unrecognized interface type %T", _interface)) - return hexutil.Bytes{} + return nil, fmt.Errorf("unrecognized interface type %T", _interface) } // EcRecover recovers the address associated with the given sig. @@ -523,6 +583,17 @@ func UnmarshalValidatorData(data interface{}) (ValidatorData, error) { }, nil } +// IsValid checks if the typed data is sound +func (typedData *TypedData) IsValid() error { + if err := typedData.Types.IsValid(); err != nil { + return err + } + if err := typedData.Domain.IsValid(); err != nil { + return err + } + return nil +} + // Map is a helper function to generate a map version of the typed data func (typedData *TypedData) Map() map[string]interface{} { dataMap := map[string]interface{}{ @@ -535,27 +606,25 @@ func (typedData *TypedData) Map() map[string]interface{} { return dataMap } -// IsValid checks if the given types object is conformant to the specs +// IsValid checks if the types object is conformant to the specs func (types *EIP712Types) IsValid() error { for typeKey, typeArr := range *types { for _, typeObj := range typeArr { typeVal := typeObj["type"] if typeKey == typeVal { - panic(fmt.Errorf("type %s cannot reference itself", typeVal)) + return fmt.Errorf("type '%s' cannot reference itself", typeVal) } - firstChar := []rune(typeVal)[0] if unicode.IsUpper(firstChar) { if (*types)[typeVal] == nil { - return fmt.Errorf("referenced type %s is undefined", typeVal) + 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) + return fmt.Errorf("referenced type '%s' must be capitalized", typeVal) } else { - return fmt.Errorf("unknown type %s", typeVal) + return fmt.Errorf("unknown atomic type '%s'", typeVal) } } } diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index ac0aab0cd7ba..745a3141c00c 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -18,6 +18,7 @@ package core import ( "context" + "encoding/json" "fmt" "math/big" "testing" @@ -153,12 +154,20 @@ func TestSignData(t *testing.T) { } func TestHashStruct(t *testing.T) { - mainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(typedData.HashStruct(typedData.PrimaryType, typedData.Message))) + hash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) + if err != nil { + t.Fatal(err) + } + mainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(hash)) if mainHash != "0xc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e" { t.Errorf("Expected different hashStruct result (got %s)", mainHash) } - domainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(typedData.HashStruct("EIP712Domain", typedData.Domain.Map()))) + hash, err = typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) + if err != nil { + t.Error(err) + } + domainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(hash)) if domainHash != "0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f" { t.Errorf("Expected different hashStruct result (got %s)", domainHash) } @@ -184,8 +193,259 @@ func TestTypeHash(t *testing.T) { } func TestEncodeData(t *testing.T) { - dataEncoding := fmt.Sprintf("0x%s", common.Bytes2Hex(typedData.EncodeData(typedData.PrimaryType, typedData.Message))) + hash, err := typedData.EncodeData(typedData.PrimaryType, typedData.Message) + if err != nil { + t.Fatal(err) + } + dataEncoding := fmt.Sprintf("0x%s", common.Bytes2Hex(hash)) if dataEncoding != "0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8cd54f074a4af31b4411ff6a60c9719dbd559c221c8ac3492d9d872b041d703d1b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8" { t.Errorf("Expected different encodeData result (got %s)", dataEncoding) } } + +func TestMalformedData1(t *testing.T) { + var data = ` + { + "types": { + "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": "Person" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": 1, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } + } + +` + var typedData TypedData + err := json.Unmarshal([]byte(data), &typedData) + if err != nil { + t.Fatalf("unmarshalling failed %v", err) + } + err = typedData.IsValid() + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + _, err = typedData.HashStruct(typedData.PrimaryType, typedData.Message) + if err.Error() != "provided data 'Hello, Bob!' doesn't match type 'Person'" { + t.Errorf("Expected `provided data 'Hello, Bob!' doesn't match type 'Person'`, got %v", err) + } +} + +func TestMalformedDomainData(t *testing.T) { + var data = ` +{ + "types": { + "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": "Blahonga" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": 1, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } + }` + var typedData TypedData + err := json.Unmarshal([]byte(data), &typedData) + if err != nil { + t.Fatalf("unmarshalling failed %v", err) + } + err = typedData.IsValid() + if err == nil { + t.Fatalf("Expected `referenced type 'Blahonga' is undefined`, got %v", err) + } + _, err = typedData.HashStruct(typedData.PrimaryType, typedData.Message) + if err.Error() != "unrecognized interface type " { + t.Errorf("Expected `unrecognized interface type `, got %v", err) + } +} + +func TestMalformedData3(t *testing.T) { + var data = ` + { + "types": { + "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" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": 1, + "vxerifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } + } + +` + var typedData TypedData + err := json.Unmarshal([]byte(data), &typedData) + if err != nil { + t.Fatalf("unmarshalling failed %v", err) + } + err = typedData.IsValid() + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + _, err = typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) + if err.Error() != "provided data '' doesn't match type 'address'" { + t.Errorf("Expected `provided data '' doesn't match type 'address'`, got %v", err) + } +} From 6d62c33bee88dd14f5c855d90ac1f755b744232b Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Fri, 9 Nov 2018 21:00:07 +0200 Subject: [PATCH 25/84] Added alphabetical sorting to type dependencies --- signer/core/signed_data.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 95454acfdc23..9566c805693b 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -24,6 +24,7 @@ import ( "math/big" "mime" "reflect" + "sort" "strconv" "strings" "unicode" @@ -329,13 +330,9 @@ func (typedData *TypedData) Dependencies(primaryType string, found []string) []s func (typedData *TypedData) EncodeType(primaryType string) hexutil.Bytes { // 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...) + slicedDeps := deps[1:] + sort.Strings(slicedDeps) + deps = append([]string{primaryType}, slicedDeps...) // Format as a string with fields var buffer bytes.Buffer From 68056e0b00dccc9c926981659a68826e356f24fe Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sat, 10 Nov 2018 00:04:46 +0200 Subject: [PATCH 26/84] Added pretty print to data/typed UI --- signer/core/cliui.go | 2 +- signer/core/signed_data.go | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/signer/core/cliui.go b/signer/core/cliui.go index 1e5927b242b0..035d4f201680 100644 --- a/signer/core/cliui.go +++ b/signer/core/cliui.go @@ -164,7 +164,7 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResp fmt.Printf("-------- Sign data request--------------\n") fmt.Printf("Account: %s\n", request.Address.String()) - fmt.Printf("message: \n%q\n", request.Message) + fmt.Printf("message: \n%v\n", request.Message) fmt.Printf("raw data: \n%v\n", request.Rawdata) fmt.Printf("message hash: %v\n", request.Hash) fmt.Printf("-------------------------------------------\n") diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 9566c805693b..4bdc4b4ae66a 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -19,6 +19,7 @@ package core import ( "bytes" "context" + "encoding/json" "errors" "fmt" "math/big" @@ -275,9 +276,12 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd if err != nil { return nil, err } - msg := fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)) - sighash := crypto.Keccak256([]byte(msg)) - req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: typedData.Map(), Message: msg, Hash: sighash} + msg, err := json.MarshalIndent(typedData.Message, "", " ") + if err != nil { + return nil, err + } + sighash := crypto.Keccak256([]byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))) + req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: typedData, Message: string(msg), Hash: sighash} signature, err := api.Sign(ctx, addr, req) if err != nil { api.UI.ShowError(err.Error()) @@ -591,7 +595,7 @@ func (typedData *TypedData) IsValid() error { return nil } -// Map is a helper function to generate a map version of the typed data +// Map generates a map version of the typed data func (typedData *TypedData) Map() map[string]interface{} { dataMap := map[string]interface{}{ "types": typedData.Types, @@ -603,6 +607,11 @@ func (typedData *TypedData) Map() map[string]interface{} { return dataMap } +// PrettyPrint generates a pretty version of the typed data +func (typedData *TypedData) PrettyPrint() string { + return "" +} + // IsValid checks if the types object is conformant to the specs func (types *EIP712Types) IsValid() error { for typeKey, typeArr := range *types { From eb03047d4280eaed04c9d959e5988d1621b012a2 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 13 Nov 2018 09:45:22 +0100 Subject: [PATCH 27/84] signer: more tests for typed data --- signer/core/signed_data_test.go | 109 ++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 745a3141c00c..4b103ea0558c 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -449,3 +449,112 @@ func TestMalformedData3(t *testing.T) { t.Errorf("Expected `provided data '' doesn't match type 'address'`, got %v", err) } } + +func TestMalformedData4(t *testing.T) { + var data = ` + { + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256 ... and now for something completely different" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Person": [ + { + "name": "name", + "type": "string" + }, + { + "name": "test", + "type": "uint8" + }, + { + "name": "wallet", + "type": "address" + } + ], + "Mail": [ + { + "name": "from", + "type": "Person" + }, + { + "name": "to", + "type": "Person" + }, + { + "name": "contents", + "type": "string" + } + ] + }, + "primaryType": "Mail", + "domain": { + "Signed by": "Bill Gates -- this text won't affect the hash'", + "we can": "stuff anything here, really", + "name": "Ether Mail", + "version": "65536", + "chainId": 1, + "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "test": 65536, + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "blahonga": "zonk bonk", + "contents": "Γ₯Γ€zΓΆ \r\n test, Bob!" + } + } +` + // The struct above contains several quirks + // 1. Using dynamic types and only validating the prefix: + //{ + // "name": "chainId", + // "type": "uint256 ... and now for something completely different" + //}, + // 2. Using dynamic types, but not verifying that the data fits into it + // "test": 65536, <-- test defined as uint8 + // 3a. Extra data in message + // "blahonga": "zonk bonk", + // 3b ... and in domain + // "Signed by": "Bill Gates", + + var typedData TypedData + err := json.Unmarshal([]byte(data), &typedData) + if err != nil { + t.Fatalf("unmarshalling failed %v", err) + } + err = typedData.IsValid() + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + hash, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) + if err == nil{ + t.Errorf("Expected error, got hash %v", hash) + }else + { + fmt.Printf("err %v", err) + } + //if err.Error() != "provided data '' doesn't match type 'address'" { + // t.Errorf("Expected `provided data '' doesn't match type 'address'`, got %v", err) + //} +} From 2308f12e00eea014015fe4a6529c77df6ac98886 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Tue, 13 Nov 2018 18:10:24 +0200 Subject: [PATCH 28/84] Fixed TestMalformedData4 errors and renamed IsValid to Validate --- signer/core/signed_data.go | 48 ++++++++++++++------------------- signer/core/signed_data_test.go | 22 +++++---------- 2 files changed, 26 insertions(+), 44 deletions(-) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 4bdc4b4ae66a..68d35768452c 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -25,6 +25,7 @@ import ( "math/big" "mime" "reflect" + "regexp" "sort" "strconv" "strings" @@ -265,7 +266,7 @@ func SignTextPlain(data hexutil.Bytes) (hexutil.Bytes, string) { // 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) { - if err := typedData.IsValid(); err != nil { + if err := typedData.Validate(); err != nil { return nil, err } domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) @@ -584,12 +585,12 @@ func UnmarshalValidatorData(data interface{}) (ValidatorData, error) { }, nil } -// IsValid checks if the typed data is sound -func (typedData *TypedData) IsValid() error { - if err := typedData.Types.IsValid(); err != nil { +// Validate checks if the typed data is sound +func (typedData *TypedData) Validate() error { + if err := typedData.Types.Validate(); err != nil { return err } - if err := typedData.Domain.IsValid(); err != nil { + if err := typedData.Domain.Validate(); err != nil { return err } return nil @@ -612,8 +613,8 @@ func (typedData *TypedData) PrettyPrint() string { return "" } -// IsValid checks if the types object is conformant to the specs -func (types *EIP712Types) IsValid() error { +// Validate checks if the types object is conformant to the specs +func (types *EIP712Types) Validate() error { for typeKey, typeArr := range *types { for _, typeObj := range typeArr { typeVal := typeObj["type"] @@ -642,35 +643,26 @@ func (types *EIP712Types) IsValid() error { // isStandardType checks if the given type is a EIP712 conformant type func isStandardTypeStr(encType string) bool { // Atomic types - for _, standardType := range []string{ - TypeAddress, - TypeBool, - TypeBytes, - TypeString, - } { - if standardType == encType { - return true - } + exp, _ := regexp.Compile(`^(address|bool|bytes|string)$`) + if (exp.MatchString(encType)) { + return true } // Dynamic types - for _, standardType := range []string{ - TypeBytes, - TypeInt, - TypeUint, - } { - if strings.HasPrefix(encType, standardType) { - return true - } + exp, _ = regexp.Compile(`^(bytes|int|uint)(\d+)$`) + if (exp.MatchString(encType)) { + return true } - // Reference types - return encType[len(encType)-1] == ']' + // Arrays + // TODO: add dynamic type arrays + exp, _ = regexp.Compile(`^(address|bool|bytes|string)\[]$`) + return exp.MatchString(encType) } -// IsValid checks if the given domain is valid, i.e. contains at least +// Validate checks if the given domain is valid, i.e. contains at least // the minimum viable keys and values -func (domain *EIP712Domain) IsValid() error { +func (domain *EIP712Domain) Validate() error { if domain.ChainId == big.NewInt(0) { return errors.New("chainId must be specified according to EIP-155") } diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 4b103ea0558c..697fd0e35e79 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -276,7 +276,7 @@ func TestMalformedData1(t *testing.T) { if err != nil { t.Fatalf("unmarshalling failed %v", err) } - err = typedData.IsValid() + err = typedData.Validate() if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -357,7 +357,7 @@ func TestMalformedDomainData(t *testing.T) { if err != nil { t.Fatalf("unmarshalling failed %v", err) } - err = typedData.IsValid() + err = typedData.Validate() if err == nil { t.Fatalf("Expected `referenced type 'Blahonga' is undefined`, got %v", err) } @@ -440,7 +440,7 @@ func TestMalformedData3(t *testing.T) { if err != nil { t.Fatalf("unmarshalling failed %v", err) } - err = typedData.IsValid() + err = typedData.Validate() if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -543,18 +543,8 @@ func TestMalformedData4(t *testing.T) { if err != nil { t.Fatalf("unmarshalling failed %v", err) } - err = typedData.IsValid() - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - hash, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) - if err == nil{ - t.Errorf("Expected error, got hash %v", hash) - }else - { - fmt.Printf("err %v", err) + err = typedData.Validate() + if err.Error() != "unknown atomic type 'uint256 ... and now for something completely different'" { + t.Fatalf("Expected `unknown atomic type 'uint256 ... and now for something completely different'`, got %v", err) } - //if err.Error() != "provided data '' doesn't match type 'address'" { - // t.Errorf("Expected `provided data '' doesn't match type 'address'`, got %v", err) - //} } From 955f822f6425421da0ccd78dbd92027c1dcc77b2 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Wed, 14 Nov 2018 00:25:18 +0200 Subject: [PATCH 29/84] Fixed more new failing tests and deanonymised some functions --- signer/core/signed_data.go | 173 +++++++++++++++----------------- signer/core/signed_data_test.go | 156 +++++++++++++++++----------- 2 files changed, 180 insertions(+), 149 deletions(-) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 68d35768452c..5cb2b3ebe8be 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -365,92 +365,28 @@ func (typedData *TypedData) TypeHash(primaryType string) hexutil.Bytes { // // each encoded member is 32-byte long func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}) (hexutil.Bytes, error) { - encTypes := []string{} encValues := []interface{}{} - // Add typehash - encTypes = append(encTypes, "bytes32") - encValues = append(encValues, typedData.TypeHash(primaryType)) - - // Generate error for a mismatch between the provided type and data - dataMismatchError := func(encType string, encValue interface{}) error { - return fmt.Errorf("provided data '%v' doesn't match type '%s'", encValue, encType) + // Verify extra data + if len(typedData.Types[primaryType]) < len(data) { + return nil, errors.New("there is extra data provided in the message") } - // Handle primitive values - handlePrimitiveValue := func(encType string, encValue interface{}) (string, interface{}, error) { - var primitiveEncType string - var primitiveEncValue interface{} - - switch encType { - case "address": - primitiveEncType = "uint160" - bytesValue := hexutil.Bytes{} - for i := 0; i < 12; i++ { - bytesValue = append(bytesValue, 0) - } - stringValue, ok := encValue.(string) - if !ok || !common.IsHexAddress(stringValue) { - return "", nil, dataMismatchError(encType, encValue) - } - for _, _byte := range common.HexToAddress(stringValue) { - bytesValue = append(bytesValue, _byte) - } - primitiveEncValue = bytesValue - case "bool": - primitiveEncType = "uint256" - var int64Val int64 - boolValue, ok := encValue.(bool) - if !ok { - return "", nil, dataMismatchError(encType, encValue) - } - if boolValue { - int64Val = 1 - } - primitiveEncValue = abi.U256(big.NewInt(int64Val)) - case "bytes", "string": - primitiveEncType = "bytes32" - bytesValue, err := bytesValueOf(encValue) - if err != nil { - return "", nil, dataMismatchError(encType, encValue) - } - primitiveEncValue = crypto.Keccak256(bytesValue) - default: - if strings.HasPrefix(encType, "bytes") { - encTypes = append(encTypes, "bytes32") - sizeStr := strings.TrimPrefix(encType, "bytes") - size, _ := strconv.Atoi(sizeStr) - bytesValue := hexutil.Bytes{} - for i := 0; i < 32-size; i++ { - bytesValue = append(bytesValue, 0) - } - if _, ok := encValue.(hexutil.Bytes); !ok { - return "", nil, dataMismatchError(encType, encValue) - } - bytesValue = append(bytesValue, encValue.(hexutil.Bytes)...) - primitiveEncValue = bytesValue - } else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { - primitiveEncType = "uint256" - bigIntValue, ok := encValue.(*big.Int) - if !ok { - return "", nil, dataMismatchError(encType, encValue) - } - primitiveEncValue = abi.U256(bigIntValue) - } - } - return primitiveEncType, primitiveEncValue, nil - } + // Add typehash + encValues = append(encValues, typedData.TypeHash(primaryType)) - // Add field contents. Structs and arrays have special handlings. + // Add field contents. Structs and arrays have special handlers. for _, field := range typedData.Types[primaryType] { encType := field["type"] encValue := data[field["name"]] if encType[len(encType)-1:] == "]" { - encTypes = append(encTypes, "bytes32") + arrayValue, ok := encValue.([]interface{}) + if !ok { + return nil, dataMismatchError(encType, encValue) + } parsedType := strings.Split(encType, "[")[0] - arrayBuffer := bytes.Buffer{} - for _, item := range encValue.([]interface{}) { + for _, item := range arrayValue { if typedData.Types[parsedType] != nil { mapValue, ok := item.(map[string]interface{}) if !ok { @@ -462,7 +398,7 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter } arrayBuffer.Write(encodedData) } else { - _, encValue, err := handlePrimitiveValue(encType, encValue) + encValue, err := handlePrimitiveValue(encType, encValue) if err != nil { return nil, err } @@ -475,7 +411,6 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter } encValues = append(encValues, crypto.Keccak256(arrayBuffer.Bytes())) } else if typedData.Types[field["type"]] != nil { - encTypes = append(encTypes, "bytes32") mapValue, ok := encValue.(map[string]interface{}) if !ok { return nil, dataMismatchError(encType, encValue) @@ -487,11 +422,10 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter encValue = crypto.Keccak256(encodedData) encValues = append(encValues, encValue) } else { - primitiveEncType, primitiveEncValue, err := handlePrimitiveValue(encType, encValue) + primitiveEncValue, err := handlePrimitiveValue(encType, encValue) if err != nil { return nil, err } - encTypes = append(encTypes, primitiveEncType) encValues = append(encValues, primitiveEncValue) } } @@ -508,6 +442,72 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter return buffer.Bytes(), nil // https://github.com/ethereumjs/ethereumjs-abi/blob/master/lib/index.js#L336 } +// handlePrimitiveValues deals with the primitive values found +// while searching through the typed data +func handlePrimitiveValue(encType string, encValue interface{}) (interface{}, error) { + var primitiveEncValue interface{} + + switch encType { + case "address": + bytesValue := hexutil.Bytes{} + for i := 0; i < 12; i++ { + bytesValue = append(bytesValue, 0) + } + stringValue, ok := encValue.(string) + if !ok || !common.IsHexAddress(stringValue) { + return nil, dataMismatchError(encType, encValue) + } + for _, _byte := range common.HexToAddress(stringValue) { + bytesValue = append(bytesValue, _byte) + } + primitiveEncValue = bytesValue + case "bool": + var int64Val int64 + boolValue, ok := encValue.(bool) + if !ok { + return nil, dataMismatchError(encType, encValue) + } + if boolValue { + int64Val = 1 + } + primitiveEncValue = abi.U256(big.NewInt(int64Val)) + case "bytes", "string": + bytesValue, err := bytesValueOf(encValue) + if err != nil { + return nil, dataMismatchError(encType, encValue) + } + primitiveEncValue = crypto.Keccak256(bytesValue) + default: + if strings.HasPrefix(encType, "bytes") { + sizeStr := strings.TrimPrefix(encType, "bytes") + size, _ := strconv.Atoi(sizeStr) + bytesValue := hexutil.Bytes{} + for i := 0; i < 32-size; i++ { + bytesValue = append(bytesValue, 0) + } + if _, ok := encValue.(hexutil.Bytes); !ok { + return nil, dataMismatchError(encType, encValue) + } + bytesValue = append(bytesValue, encValue.(hexutil.Bytes)...) + primitiveEncValue = bytesValue + } else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { + bigIntValue, ok := encValue.(*big.Int) + if !ok { + return nil, dataMismatchError(encType, encValue) + } + primitiveEncValue = abi.U256(bigIntValue) + } + } + return primitiveEncValue, nil +} + +// dataMismatchError generates an error for a mismatch between +// the provided type and data +func dataMismatchError(encType string, encValue interface{}) error { + return fmt.Errorf("provided data '%v' doesn't match type '%s'", encValue, encType) +} + +// bytesValuesOf returns the bytes value of the given interface func bytesValueOf(_interface interface{}) (hexutil.Bytes, error) { bytesValue, ok := _interface.(hexutil.Bytes) if ok { @@ -585,7 +585,7 @@ func UnmarshalValidatorData(data interface{}) (ValidatorData, error) { }, nil } -// Validate checks if the typed data is sound +// Validate make sure the types are sound func (typedData *TypedData) Validate() error { if err := typedData.Types.Validate(); err != nil { return err @@ -604,15 +604,9 @@ func (typedData *TypedData) Map() map[string]interface{} { "primaryType": typedData.PrimaryType, "message": typedData.Message, } - return dataMap } -// PrettyPrint generates a pretty version of the typed data -func (typedData *TypedData) PrettyPrint() string { - return "" -} - // Validate checks if the types object is conformant to the specs func (types *EIP712Types) Validate() error { for typeKey, typeArr := range *types { @@ -644,19 +638,18 @@ func (types *EIP712Types) Validate() error { func isStandardTypeStr(encType string) bool { // Atomic types exp, _ := regexp.Compile(`^(address|bool|bytes|string)$`) - if (exp.MatchString(encType)) { + if exp.MatchString(encType) { return true } // Dynamic types exp, _ = regexp.Compile(`^(bytes|int|uint)(\d+)$`) - if (exp.MatchString(encType)) { + if exp.MatchString(encType) { return true } // Arrays - // TODO: add dynamic type arrays - exp, _ = regexp.Compile(`^(address|bool|bytes|string)\[]$`) + exp, _ = regexp.Compile(`^(address|bool|bytes|string|((bytes|int|uint)(\d+)))\[]$`) return exp.MatchString(encType) } diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 697fd0e35e79..5e130db1f42a 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -204,7 +204,14 @@ func TestEncodeData(t *testing.T) { } func TestMalformedData1(t *testing.T) { - var data = ` + // Verifies that malformed domain keys are properly caught: + //{ + // "name": "Ether Mail", + // "version": "1", + // "chainId": 1, + // "vxerifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + //} + var jsonTypedData = ` { "types": { "EIP712Domain": [ @@ -246,7 +253,7 @@ func TestMalformedData1(t *testing.T) { }, { "name": "contents", - "type": "Person" + "type": "string" } ] }, @@ -255,7 +262,7 @@ func TestMalformedData1(t *testing.T) { "name": "Ether Mail", "version": "1", "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + "vxerifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" }, "message": { "from": { @@ -271,24 +278,39 @@ func TestMalformedData1(t *testing.T) { } ` - var typedData TypedData - err := json.Unmarshal([]byte(data), &typedData) + var malformedTypedData TypedData + err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) if err != nil { t.Fatalf("unmarshalling failed %v", err) } - err = typedData.Validate() + err = malformedTypedData.Validate() if err != nil { t.Fatalf("Expected no error, got %v", err) } - _, err = typedData.HashStruct(typedData.PrimaryType, typedData.Message) - if err.Error() != "provided data 'Hello, Bob!' doesn't match type 'Person'" { - t.Errorf("Expected `provided data 'Hello, Bob!' doesn't match type 'Person'`, got %v", err) + _, err = malformedTypedData.HashStruct("EIP712Domain", malformedTypedData.Domain.Map()) + if err == nil || err.Error() != "provided data '' doesn't match type 'address'" { + t.Errorf("Expected `provided data '' doesn't match type 'address'`, got %v", err) } } -func TestMalformedDomainData(t *testing.T) { - var data = ` -{ +func TestMalformedData2(t *testing.T) { + // Verifies that: + // 1. Mismatches between the given type and data, i.e. `Person` and + // the data item is a string, are properly caught: + //{ + // "name": "contents", + // "type": "Person" + //}, + //{ + // "contents": "Hello, Bob!" <-- string not "Person" + //} + // 2. Nonexistent types are properly caught: + //{ + // "name": "contents", + // "type": "Blahonga" + //} + jsonTypedData := ` + { "types": { "EIP712Domain": [ { @@ -329,7 +351,7 @@ func TestMalformedDomainData(t *testing.T) { }, { "name": "contents", - "type": "Blahonga" + "type": "Person" } ] }, @@ -351,24 +373,45 @@ func TestMalformedDomainData(t *testing.T) { }, "contents": "Hello, Bob!" } - }` - var typedData TypedData - err := json.Unmarshal([]byte(data), &typedData) + } +` + var malformedTypedData TypedData + err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) if err != nil { t.Fatalf("unmarshalling failed %v", err) } - err = typedData.Validate() - if err == nil { + err = malformedTypedData.Validate() + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) + if err.Error() != "provided data 'Hello, Bob!' doesn't match type 'Person'" { + t.Errorf("Expected `provided data 'Hello, Bob!' doesn't match type 'Person'`, got %v", err) + } + + malformedTypedData.Types["Mail"][2]["type"] = "Blahonga" + err = malformedTypedData.Validate() + if err == nil || err.Error() != "referenced type 'Blahonga' is undefined" { t.Fatalf("Expected `referenced type 'Blahonga' is undefined`, got %v", err) } - _, err = typedData.HashStruct(typedData.PrimaryType, typedData.Message) - if err.Error() != "unrecognized interface type " { + _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) + if err == nil || err.Error() != "unrecognized interface type " { t.Errorf("Expected `unrecognized interface type `, got %v", err) } } func TestMalformedData3(t *testing.T) { - var data = ` + // Verifies several quirks + // 1. Using dynamic types and only validating the prefix: + //{ + // "name": "chainId", + // "type": "uint256 ... and now for something completely different" + //} + // 2. Extra data in message: + //{ + // "blahonga": "zonk bonk" + //} + jsonTypedData := ` { "types": { "EIP712Domain": [ @@ -382,7 +425,7 @@ func TestMalformedData3(t *testing.T) { }, { "name": "chainId", - "type": "uint256" + "type": "uint256 ... and now for something completely different" }, { "name": "verifyingContract", @@ -419,12 +462,12 @@ func TestMalformedData3(t *testing.T) { "name": "Ether Mail", "version": "1", "chainId": 1, - "vxerifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" }, "message": { "from": { "name": "Cow", - "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + "wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" }, "to": { "name": "Bob", @@ -433,25 +476,31 @@ func TestMalformedData3(t *testing.T) { "contents": "Hello, Bob!" } } - ` - var typedData TypedData - err := json.Unmarshal([]byte(data), &typedData) + var malformedTypedData TypedData + err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) if err != nil { t.Fatalf("unmarshalling failed %v", err) } - err = typedData.Validate() - if err != nil { - t.Fatalf("Expected no error, got %v", err) + err = malformedTypedData.Validate() + if err == nil || err.Error() != "unknown atomic type 'uint256 ... and now for something completely different'" { + t.Fatalf("Expected `unknown atomic type 'uint256 ... and now for something completely different'`, got %v", err) } - _, err = typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) - if err.Error() != "provided data '' doesn't match type 'address'" { - t.Errorf("Expected `provided data '' doesn't match type 'address'`, got %v", err) + + malformedTypedData.Types["EIP712Domain"][2]["type"] = "uint256" + malformedTypedData.Message["blahonga"] = "zonk bonk" + _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) + if err == nil || err.Error() != "there is extra data provided in the message" { + t.Errorf("Expected `there is extra data provided in the message`, got %v", err) } } func TestMalformedData4(t *testing.T) { - var data = ` + // Verifies data that doesn't fit into it: + //{ + // "test": 65536 <-- test defined as uint8 + //} + jsonTypedData := ` { "types": { "EIP712Domain": [ @@ -465,7 +514,7 @@ func TestMalformedData4(t *testing.T) { }, { "name": "chainId", - "type": "uint256 ... and now for something completely different" + "type": "uint256" }, { "name": "verifyingContract", @@ -503,48 +552,37 @@ func TestMalformedData4(t *testing.T) { }, "primaryType": "Mail", "domain": { - "Signed by": "Bill Gates -- this text won't affect the hash'", - "we can": "stuff anything here, really", "name": "Ether Mail", - "version": "65536", + "version": "1", "chainId": 1, "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" }, "message": { "from": { "name": "Cow", + "test": 65536, "wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" }, "to": { "name": "Bob", - "test": 65536, "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" }, - "blahonga": "zonk bonk", - "contents": "Γ₯Γ€zΓΆ \r\n test, Bob!" + "contents": "Hello, Bob!" } } ` - // The struct above contains several quirks - // 1. Using dynamic types and only validating the prefix: - //{ - // "name": "chainId", - // "type": "uint256 ... and now for something completely different" - //}, - // 2. Using dynamic types, but not verifying that the data fits into it - // "test": 65536, <-- test defined as uint8 - // 3a. Extra data in message - // "blahonga": "zonk bonk", - // 3b ... and in domain - // "Signed by": "Bill Gates", - - var typedData TypedData - err := json.Unmarshal([]byte(data), &typedData) + var malformedTypedData TypedData + err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) if err != nil { t.Fatalf("unmarshalling failed %v", err) } - err = typedData.Validate() - if err.Error() != "unknown atomic type 'uint256 ... and now for something completely different'" { - t.Fatalf("Expected `unknown atomic type 'uint256 ... and now for something completely different'`, got %v", err) + err = malformedTypedData.Validate() + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) + if err == nil || err.Error() != "provided data '65536' doesn't match type 'uint8'" { + t.Fatalf("Expected `provided data '65536' doesn't match type 'uint8'`, got %v", err) } } From 3742cf23804f583d9edfc6e50c1faf763234f192 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Wed, 14 Nov 2018 14:16:23 +0200 Subject: [PATCH 30/84] Added types to EIP712 output in cliui --- cmd/clef/main.go | 2 +- signer/core/signed_data.go | 54 +++++++++++++++++++++++---------- signer/core/signed_data_test.go | 6 ++-- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/cmd/clef/main.go b/cmd/clef/main.go index d5a2dab54322..f3464b2ff6a3 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -56,7 +56,7 @@ import ( const ExternalAPIVersion = "5.0.0" // InternalAPIVersion -- see intapi_changelog.md -const InternalAPIVersion = "3.0.0" +const InternalAPIVersion = "3.1.0" const legalWarning = ` WARNING! diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 5cb2b3ebe8be..f2c8bc005787 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -19,7 +19,6 @@ package core import ( "bytes" "context" - "encoding/json" "errors" "fmt" "math/big" @@ -75,6 +74,7 @@ type TypedData struct { PrimaryType string `json:"primaryType"` Domain EIP712Domain `json:"domain"` Message EIP712Data `json:"message"` + Output bytes.Buffer } type EIP712Type []map[string]string @@ -273,16 +273,16 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd if err != nil { return nil, err } + typedData.Output.Reset() + typedData.Output.WriteString(fmt.Sprintf("%s {\n", typedData.PrimaryType)) typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) if err != nil { return nil, err } - msg, err := json.MarshalIndent(typedData.Message, "", " ") - if err != nil { - return nil, err - } + typedData.Output.Truncate(typedData.Output.Len() - 2) + typedData.Output.WriteString(fmt.Sprintf("\n}")) sighash := crypto.Keccak256([]byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))) - req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: typedData, Message: string(msg), Hash: sighash} + req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: typedData.Map(), Message: typedData.Output.String(), Hash: sighash} signature, err := api.Sign(ctx, addr, req) if err != nil { api.UI.ShowError(err.Error()) @@ -293,7 +293,7 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd // HashStruct generates a keccak256 hash of the encoding of the provided data func (typedData *TypedData) HashStruct(primaryType string, data EIP712Data) (hexutil.Bytes, error) { - encodedData, err := typedData.EncodeData(primaryType, data) + encodedData, err := typedData.EncodeData(primaryType, data, 1) if err != nil { return nil, err } @@ -364,7 +364,7 @@ func (typedData *TypedData) TypeHash(primaryType string) hexutil.Bytes { // `enc(value₁) β€– enc(valueβ‚‚) β€– … β€– enc(valueβ‚™)` // // each encoded member is 32-byte long -func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}) (hexutil.Bytes, error) { +func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}, depth int) (hexutil.Bytes, error) { encValues := []interface{}{} // Verify extra data @@ -384,21 +384,24 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter if !ok { return nil, dataMismatchError(encType, encValue) } - parsedType := strings.Split(encType, "[")[0] + typedData.Output.WriteString(strings.Repeat("\u00a0", depth*2)) + typedData.Output.WriteString(fmt.Sprintf("\"%s\": [ %s\n", field["name"], encType)) + arrayBuffer := bytes.Buffer{} + parsedType := strings.Split(encType, "[")[0] for _, item := range arrayValue { if typedData.Types[parsedType] != nil { mapValue, ok := item.(map[string]interface{}) if !ok { return nil, dataMismatchError(parsedType, item) } - encodedData, err := typedData.EncodeData(parsedType, mapValue) + encodedData, err := typedData.EncodeData(parsedType, mapValue, depth+1) if err != nil { return nil, err } arrayBuffer.Write(encodedData) } else { - encValue, err := handlePrimitiveValue(encType, encValue) + encValue, err := typedData.HandlePrimitiveValue(encType, encValue, depth) if err != nil { return nil, err } @@ -409,20 +412,29 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter arrayBuffer.Write(bytesValue) } } + + typedData.Output.Truncate(typedData.Output.Len() - 2) + typedData.Output.WriteString(fmt.Sprintf("\n%s],\n", strings.Repeat("\u00a0", depth*2))) encValues = append(encValues, crypto.Keccak256(arrayBuffer.Bytes())) } else if typedData.Types[field["type"]] != nil { mapValue, ok := encValue.(map[string]interface{}) if !ok { return nil, dataMismatchError(encType, encValue) } - encodedData, err := typedData.EncodeData(field["type"], mapValue) + typedData.Output.WriteString(strings.Repeat("\u00a0", depth*2)) + typedData.Output.WriteString(fmt.Sprintf("\"%s\": { %s\n", field["name"], encType)) + + encodedData, err := typedData.EncodeData(field["type"], mapValue, depth+1) if err != nil { return nil, err } + + typedData.Output.Truncate(typedData.Output.Len() - 2) + typedData.Output.WriteString(fmt.Sprintf("\n%s},\n", strings.Repeat("\u00a0", depth*2))) encValue = crypto.Keccak256(encodedData) encValues = append(encValues, encValue) } else { - primitiveEncValue, err := handlePrimitiveValue(encType, encValue) + primitiveEncValue, err := typedData.HandlePrimitiveValue(encType, encValue, depth) if err != nil { return nil, err } @@ -444,9 +456,11 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter // handlePrimitiveValues deals with the primitive values found // while searching through the typed data -func handlePrimitiveValue(encType string, encValue interface{}) (interface{}, error) { +func (typedData *TypedData) HandlePrimitiveValue(encType string, encValue interface{}, depth int) (interface{}, error) { var primitiveEncValue interface{} + typedData.Output.WriteString(strings.Repeat("\u00a0", depth*2)) + typedData.Output.WriteString(fmt.Sprintf("\"%s\": ", encType)) switch encType { case "address": bytesValue := hexutil.Bytes{} @@ -457,10 +471,12 @@ func handlePrimitiveValue(encType string, encValue interface{}) (interface{}, er if !ok || !common.IsHexAddress(stringValue) { return nil, dataMismatchError(encType, encValue) } - for _, _byte := range common.HexToAddress(stringValue) { + addressValue := common.HexToAddress(stringValue) + for _, _byte := range addressValue { bytesValue = append(bytesValue, _byte) } primitiveEncValue = bytesValue + typedData.Output.WriteString(fmt.Sprintf("%s,\n", addressValue.String())) case "bool": var int64Val int64 boolValue, ok := encValue.(bool) @@ -471,12 +487,14 @@ func handlePrimitiveValue(encType string, encValue interface{}) (interface{}, er int64Val = 1 } primitiveEncValue = abi.U256(big.NewInt(int64Val)) + typedData.Output.WriteString(fmt.Sprintf("%t,\n", boolValue)) case "bytes", "string": bytesValue, err := bytesValueOf(encValue) if err != nil { return nil, dataMismatchError(encType, encValue) } primitiveEncValue = crypto.Keccak256(bytesValue) + typedData.Output.WriteString(fmt.Sprintf("\"%s\",\n", encValue)) default: if strings.HasPrefix(encType, "bytes") { sizeStr := strings.TrimPrefix(encType, "bytes") @@ -490,12 +508,16 @@ func handlePrimitiveValue(encType string, encValue interface{}) (interface{}, er } bytesValue = append(bytesValue, encValue.(hexutil.Bytes)...) primitiveEncValue = bytesValue + typedData.Output.WriteString(fmt.Sprintf("\"%s\",\n", encValue)) } else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { bigIntValue, ok := encValue.(*big.Int) if !ok { return nil, dataMismatchError(encType, encValue) } primitiveEncValue = abi.U256(bigIntValue) + typedData.Output.WriteString(fmt.Sprintf("%d,\n", bigIntValue)) + } else { + return nil, fmt.Errorf("unrecognized type '%s'", encType) } } return primitiveEncValue, nil @@ -525,7 +547,7 @@ func bytesValueOf(_interface interface{}) (hexutil.Bytes, error) { break } - return nil, fmt.Errorf("unrecognized interface type %T", _interface) + return nil, fmt.Errorf("unrecognized type '%T'", _interface) } // EcRecover recovers the address associated with the given sig. diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 5e130db1f42a..936f0fbb3695 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -193,7 +193,7 @@ func TestTypeHash(t *testing.T) { } func TestEncodeData(t *testing.T) { - hash, err := typedData.EncodeData(typedData.PrimaryType, typedData.Message) + hash, err := typedData.EncodeData(typedData.PrimaryType, typedData.Message, 0) if err != nil { t.Fatal(err) } @@ -395,8 +395,8 @@ func TestMalformedData2(t *testing.T) { t.Fatalf("Expected `referenced type 'Blahonga' is undefined`, got %v", err) } _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) - if err == nil || err.Error() != "unrecognized interface type " { - t.Errorf("Expected `unrecognized interface type `, got %v", err) + if err == nil || err.Error() != "unrecognized type 'Blahonga'" { + t.Errorf("Expected `unrecognized type 'Blahonga'`, got %v", err) } } From 4659c180ba02454e7625cbd152c2be098eb250c3 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Tue, 20 Nov 2018 11:47:09 +0200 Subject: [PATCH 31/84] Fixed regexp issues --- signer/core/signed_data.go | 88 ++++++++++++--------------------- signer/core/signed_data_test.go | 6 +-- 2 files changed, 35 insertions(+), 59 deletions(-) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index f2c8bc005787..2d26ec80493e 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -70,25 +70,25 @@ type ValidatorData struct { } type TypedData struct { - Types EIP712Types `json:"types"` - PrimaryType string `json:"primaryType"` - Domain EIP712Domain `json:"domain"` - Message EIP712Data `json:"message"` + Types Types `json:"types"` + PrimaryType string `json:"primaryType"` + Domain TypedDataDomain `json:"domain"` + Message TypedDataMessage `json:"message"` Output bytes.Buffer } -type EIP712Type []map[string]string +type Type []map[string]string -type EIP712Types map[string]EIP712Type +type Types map[string]Type -type EIP712TypePriority struct { +type TypePriority struct { Type string Value uint } -type EIP712Data = map[string]interface{} +type TypedDataMessage = map[string]interface{} -type EIP712Domain struct { +type TypedDataDomain struct { Name string `json:"name"` Version string `json:"version"` ChainId *big.Int `json:"chainId"` @@ -96,14 +96,7 @@ type EIP712Domain struct { Salt string `json:"salt"` } -const ( - TypeAddress = "address" - TypeBool = "bool" - TypeBytes = "bytes" - TypeInt = "int" - TypeString = "string" - TypeUint = "uint" -) +var typedDataRegexp = regexp.MustCompile(`^((address|bool|bytes|string)|((bytes)([1-9]|[1-2][0-9]|3[0-2]))|((int|uint)(8|16|32|64|128|256)))(\[])?$`) // Sign receives a request and produces a signature @@ -292,7 +285,7 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd } // HashStruct generates a keccak256 hash of the encoding of the provided data -func (typedData *TypedData) HashStruct(primaryType string, data EIP712Data) (hexutil.Bytes, error) { +func (typedData *TypedData) HashStruct(primaryType string, data TypedDataMessage) (hexutil.Bytes, error) { encodedData, err := typedData.EncodeData(primaryType, data, 1) if err != nil { return nil, err @@ -365,7 +358,7 @@ func (typedData *TypedData) TypeHash(primaryType string) hexutil.Bytes { // // each encoded member is 32-byte long func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}, depth int) (hexutil.Bytes, error) { - encValues := []interface{}{} + buffer := bytes.Buffer{} // Verify extra data if len(typedData.Types[primaryType]) < len(data) { @@ -373,7 +366,7 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter } // Add typehash - encValues = append(encValues, typedData.TypeHash(primaryType)) + buffer.Write(typedData.TypeHash(primaryType)) // Add field contents. Structs and arrays have special handlers. for _, field := range typedData.Types[primaryType] { @@ -415,7 +408,7 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter typedData.Output.Truncate(typedData.Output.Len() - 2) typedData.Output.WriteString(fmt.Sprintf("\n%s],\n", strings.Repeat("\u00a0", depth*2))) - encValues = append(encValues, crypto.Keccak256(arrayBuffer.Bytes())) + buffer.Write(crypto.Keccak256(arrayBuffer.Bytes())) } else if typedData.Types[field["type"]] != nil { mapValue, ok := encValue.(map[string]interface{}) if !ok { @@ -431,24 +424,18 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter typedData.Output.Truncate(typedData.Output.Len() - 2) typedData.Output.WriteString(fmt.Sprintf("\n%s},\n", strings.Repeat("\u00a0", depth*2))) - encValue = crypto.Keccak256(encodedData) - encValues = append(encValues, encValue) + buffer.Write(crypto.Keccak256(encodedData)) } else { primitiveEncValue, err := typedData.HandlePrimitiveValue(encType, encValue, depth) if err != nil { return nil, err } - encValues = append(encValues, primitiveEncValue) - } - } - - buffer := bytes.Buffer{} - for _, encValue := range encValues { - bytesValue, err := bytesValueOf(encValue) - if err != nil { - return nil, err + bytesValue, err := bytesValueOf(primitiveEncValue) + if err != nil { + return nil, err + } + buffer.Write(bytesValue) } - buffer.Write(bytesValue) } return buffer.Bytes(), nil // https://github.com/ethereumjs/ethereumjs-abi/blob/master/lib/index.js#L336 @@ -539,6 +526,8 @@ func bytesValueOf(_interface interface{}) (hexutil.Bytes, error) { switch reflect.TypeOf(_interface) { case reflect.TypeOf(hexutil.Bytes{}): return _interface.(hexutil.Bytes), nil + case reflect.TypeOf([]byte{}): + return hexutil.Bytes(_interface.([]byte)), nil case reflect.TypeOf([]uint8{}): return _interface.([]uint8), nil case reflect.TypeOf(string("")): @@ -584,6 +573,9 @@ func UnmarshalValidatorData(data interface{}) (ValidatorData, error) { raw := data.(map[string]interface{}) addr, ok := raw["address"].(string) + if !ok { + return ValidatorData{}, errors.New("validator address is not sent as a string") + } addrBytes, err := hexutil.Decode(addr) if err != nil { return ValidatorData{}, err @@ -593,6 +585,9 @@ func UnmarshalValidatorData(data interface{}) (ValidatorData, error) { } message, ok := raw["message"].(string) + if !ok { + return ValidatorData{}, errors.New("message is not sent as a string") + } messageBytes, err := hexutil.Decode(message) if err != nil { return ValidatorData{}, err @@ -630,7 +625,7 @@ func (typedData *TypedData) Map() map[string]interface{} { } // Validate checks if the types object is conformant to the specs -func (types *EIP712Types) Validate() error { +func (types *Types) Validate() error { for typeKey, typeArr := range *types { for _, typeObj := range typeArr { typeVal := typeObj["type"] @@ -643,7 +638,7 @@ func (types *EIP712Types) Validate() error { return fmt.Errorf("referenced type '%s' is undefined", typeVal) } } else { - if !isStandardTypeStr(typeVal) { + if !typedDataRegexp.MatchString(typeVal) { if (*types)[typeVal] != nil { return fmt.Errorf("referenced type '%s' must be capitalized", typeVal) } else { @@ -656,28 +651,9 @@ func (types *EIP712Types) Validate() error { return nil } -// isStandardType checks if the given type is a EIP712 conformant type -func isStandardTypeStr(encType string) bool { - // Atomic types - exp, _ := regexp.Compile(`^(address|bool|bytes|string)$`) - if exp.MatchString(encType) { - return true - } - - // Dynamic types - exp, _ = regexp.Compile(`^(bytes|int|uint)(\d+)$`) - if exp.MatchString(encType) { - return true - } - - // Arrays - exp, _ = regexp.Compile(`^(address|bool|bytes|string|((bytes|int|uint)(\d+)))\[]$`) - return exp.MatchString(encType) -} - // Validate checks if the given domain is valid, i.e. contains at least // the minimum viable keys and values -func (domain *EIP712Domain) Validate() error { +func (domain *TypedDataDomain) Validate() error { if domain.ChainId == big.NewInt(0) { return errors.New("chainId must be specified according to EIP-155") } @@ -690,7 +666,7 @@ func (domain *EIP712Domain) Validate() error { } // Map is a helper function to generate a map version of the domain -func (domain *EIP712Domain) Map() map[string]interface{} { +func (domain *TypedDataDomain) Map() map[string]interface{} { dataMap := map[string]interface{}{ "chainId": domain.ChainId, } diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 936f0fbb3695..e6b6535effb2 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -28,7 +28,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ) -var typesStandard = EIP712Types{ +var typesStandard = Types{ "EIP712Domain": { { "name": "name", @@ -75,7 +75,7 @@ var typesStandard = EIP712Types{ const primaryType = "Mail" -var domainStandard = EIP712Domain{ +var domainStandard = TypedDataDomain{ "Ether Mail", "1", big.NewInt(1), @@ -169,7 +169,7 @@ func TestHashStruct(t *testing.T) { } domainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(hash)) if domainHash != "0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f" { - t.Errorf("Expected different hashStruct result (got %s)", domainHash) + t.Errorf("Expected different domain hashStruct result (got %s)", domainHash) } } From d2bebf415c6c2a5cfb95bd4d9a3c34e96cc33dff Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Tue, 20 Nov 2018 12:58:32 +0200 Subject: [PATCH 32/84] Added pseudo-failing test --- signer/core/signed_data_test.go | 154 +++++++++++++++++--------------- 1 file changed, 83 insertions(+), 71 deletions(-) diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index e6b6535effb2..80e93805455a 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -73,6 +73,78 @@ var typesStandard = Types{ }, } +var jsonTypedData = ` + { + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Person": [ + { + "name": "name", + "type": "string" + }, + { + "name": "test", + "type": "uint8" + }, + { + "name": "wallet", + "type": "address" + } + ], + "Mail": [ + { + "name": "from", + "type": "Person" + }, + { + "name": "to", + "type": "Person" + }, + { + "name": "contents", + "type": "string" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": 1, + "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "test": 3, + "wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } + } +` + const primaryType = "Mail" var domainStandard = TypedDataDomain{ @@ -500,82 +572,14 @@ func TestMalformedData4(t *testing.T) { //{ // "test": 65536 <-- test defined as uint8 //} - jsonTypedData := ` - { - "types": { - "EIP712Domain": [ - { - "name": "name", - "type": "string" - }, - { - "name": "version", - "type": "string" - }, - { - "name": "chainId", - "type": "uint256" - }, - { - "name": "verifyingContract", - "type": "address" - } - ], - "Person": [ - { - "name": "name", - "type": "string" - }, - { - "name": "test", - "type": "uint8" - }, - { - "name": "wallet", - "type": "address" - } - ], - "Mail": [ - { - "name": "from", - "type": "Person" - }, - { - "name": "to", - "type": "Person" - }, - { - "name": "contents", - "type": "string" - } - ] - }, - "primaryType": "Mail", - "domain": { - "name": "Ether Mail", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "from": { - "name": "Cow", - "test": 65536, - "wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to": { - "name": "Bob", - "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents": "Hello, Bob!" - } - } -` var malformedTypedData TypedData err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) if err != nil { t.Fatalf("unmarshalling failed %v", err) } + // Set test to something outside uint8 + (malformedTypedData.Message["from"]).(map[string]interface{})["test"] = 65536 + err = malformedTypedData.Validate() if err != nil { t.Fatalf("Expected no error, got %v", err) @@ -585,4 +589,12 @@ func TestMalformedData4(t *testing.T) { if err == nil || err.Error() != "provided data '65536' doesn't match type 'uint8'" { t.Fatalf("Expected `provided data '65536' doesn't match type 'uint8'`, got %v", err) } + + // Set it to something that should work + (malformedTypedData.Message["from"]).(map[string]interface{})["test"] = 3 + + _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) + if err != nil { + t.Fatalf("Expected no err, got %v", err) + } } From 4239277711c74d73468658ffd67d9f770824699b Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Wed, 21 Nov 2018 15:03:42 +0200 Subject: [PATCH 33/84] Fixed false positive test --- signer/core/signed_data_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 80e93805455a..6761231c527d 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -590,8 +590,8 @@ func TestMalformedData4(t *testing.T) { t.Fatalf("Expected `provided data '65536' doesn't match type 'uint8'`, got %v", err) } - // Set it to something that should work - (malformedTypedData.Message["from"]).(map[string]interface{})["test"] = 3 + (malformedTypedData.Message["from"]).(map[string]interface{})["test"] = big.NewInt(3) + (malformedTypedData.Message["to"]).(map[string]interface{})["test"] = big.NewInt(4) _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) if err != nil { From 5faa28ad33f0137df7e240703d7ccd4345629eec Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Tue, 27 Nov 2018 01:07:00 +0200 Subject: [PATCH 34/84] Added PrettyPrint method --- signer/core/signed_data.go | 122 +++++++++++++++++++++++++++++-------- 1 file changed, 96 insertions(+), 26 deletions(-) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 2d26ec80493e..39e78ea46b97 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -74,7 +74,6 @@ type TypedData struct { PrimaryType string `json:"primaryType"` Domain TypedDataDomain `json:"domain"` Message TypedDataMessage `json:"message"` - Output bytes.Buffer } type Type []map[string]string @@ -266,16 +265,13 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd if err != nil { return nil, err } - typedData.Output.Reset() - typedData.Output.WriteString(fmt.Sprintf("%s {\n", typedData.PrimaryType)) typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) if err != nil { return nil, err } - typedData.Output.Truncate(typedData.Output.Len() - 2) - typedData.Output.WriteString(fmt.Sprintf("\n}")) sighash := crypto.Keccak256([]byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))) - req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: typedData.Map(), Message: typedData.Output.String(), Hash: sighash} + output := typedData.PrettyPrint() + req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: typedData.Map(), Message: output, Hash: sighash} signature, err := api.Sign(ctx, addr, req) if err != nil { api.UI.ShowError(err.Error()) @@ -377,8 +373,6 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter if !ok { return nil, dataMismatchError(encType, encValue) } - typedData.Output.WriteString(strings.Repeat("\u00a0", depth*2)) - typedData.Output.WriteString(fmt.Sprintf("\"%s\": [ %s\n", field["name"], encType)) arrayBuffer := bytes.Buffer{} parsedType := strings.Split(encType, "[")[0] @@ -394,7 +388,7 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter } arrayBuffer.Write(encodedData) } else { - encValue, err := typedData.HandlePrimitiveValue(encType, encValue, depth) + encValue, err := typedData.EncodePrimitiveValue(encType, encValue, depth) if err != nil { return nil, err } @@ -406,27 +400,21 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter } } - typedData.Output.Truncate(typedData.Output.Len() - 2) - typedData.Output.WriteString(fmt.Sprintf("\n%s],\n", strings.Repeat("\u00a0", depth*2))) buffer.Write(crypto.Keccak256(arrayBuffer.Bytes())) } else if typedData.Types[field["type"]] != nil { mapValue, ok := encValue.(map[string]interface{}) if !ok { return nil, dataMismatchError(encType, encValue) } - typedData.Output.WriteString(strings.Repeat("\u00a0", depth*2)) - typedData.Output.WriteString(fmt.Sprintf("\"%s\": { %s\n", field["name"], encType)) encodedData, err := typedData.EncodeData(field["type"], mapValue, depth+1) if err != nil { return nil, err } - typedData.Output.Truncate(typedData.Output.Len() - 2) - typedData.Output.WriteString(fmt.Sprintf("\n%s},\n", strings.Repeat("\u00a0", depth*2))) buffer.Write(crypto.Keccak256(encodedData)) } else { - primitiveEncValue, err := typedData.HandlePrimitiveValue(encType, encValue, depth) + primitiveEncValue, err := typedData.EncodePrimitiveValue(encType, encValue, depth) if err != nil { return nil, err } @@ -438,16 +426,14 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter } } - return buffer.Bytes(), nil // https://github.com/ethereumjs/ethereumjs-abi/blob/master/lib/index.js#L336 + return buffer.Bytes(), nil } -// handlePrimitiveValues deals with the primitive values found +// EncodePrimitiveValue deals with the primitive values found // while searching through the typed data -func (typedData *TypedData) HandlePrimitiveValue(encType string, encValue interface{}, depth int) (interface{}, error) { +func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interface{}, depth int) (interface{}, error) { var primitiveEncValue interface{} - typedData.Output.WriteString(strings.Repeat("\u00a0", depth*2)) - typedData.Output.WriteString(fmt.Sprintf("\"%s\": ", encType)) switch encType { case "address": bytesValue := hexutil.Bytes{} @@ -463,7 +449,6 @@ func (typedData *TypedData) HandlePrimitiveValue(encType string, encValue interf bytesValue = append(bytesValue, _byte) } primitiveEncValue = bytesValue - typedData.Output.WriteString(fmt.Sprintf("%s,\n", addressValue.String())) case "bool": var int64Val int64 boolValue, ok := encValue.(bool) @@ -474,14 +459,12 @@ func (typedData *TypedData) HandlePrimitiveValue(encType string, encValue interf int64Val = 1 } primitiveEncValue = abi.U256(big.NewInt(int64Val)) - typedData.Output.WriteString(fmt.Sprintf("%t,\n", boolValue)) case "bytes", "string": bytesValue, err := bytesValueOf(encValue) if err != nil { return nil, dataMismatchError(encType, encValue) } primitiveEncValue = crypto.Keccak256(bytesValue) - typedData.Output.WriteString(fmt.Sprintf("\"%s\",\n", encValue)) default: if strings.HasPrefix(encType, "bytes") { sizeStr := strings.TrimPrefix(encType, "bytes") @@ -495,14 +478,12 @@ func (typedData *TypedData) HandlePrimitiveValue(encType string, encValue interf } bytesValue = append(bytesValue, encValue.(hexutil.Bytes)...) primitiveEncValue = bytesValue - typedData.Output.WriteString(fmt.Sprintf("\"%s\",\n", encValue)) } else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { bigIntValue, ok := encValue.(*big.Int) if !ok { return nil, dataMismatchError(encType, encValue) } primitiveEncValue = abi.U256(bigIntValue) - typedData.Output.WriteString(fmt.Sprintf("%d,\n", bigIntValue)) } else { return nil, fmt.Errorf("unrecognized type '%s'", encType) } @@ -624,6 +605,95 @@ func (typedData *TypedData) Map() map[string]interface{} { return dataMap } +// PrettyPrint generates a nice output to help the users +// of clef present data in their apps +func (typedData *TypedData) PrettyPrint() string { + output := bytes.Buffer{} + + output.WriteString(fmt.Sprintf("%s {\n", "Domain")) + output.WriteString(typedData.PrettyPrintData("EIP712Domain", typedData.Domain.Map(), 1)) + output.Truncate(output.Len() - 2) + output.WriteString(fmt.Sprintf("\n}\n")) + + output.WriteString(fmt.Sprintf("%s {\n", typedData.PrimaryType)) + output.WriteString(typedData.PrettyPrintData(typedData.PrimaryType, typedData.Message, 1)) + output.Truncate(output.Len() - 2) + output.WriteString(fmt.Sprintf("\n}")) + + return output.String() +} + +// PrettyPrintData generates a formatted output for the +// given data +func (typedData *TypedData) PrettyPrintData(primaryType string, data map[string]interface{}, depth int) string { + output := bytes.Buffer{} + + // Add field contents. Structs and arrays have special handlers. + for _, field := range typedData.Types[primaryType] { + encType := field["type"] + encName := field["name"] + encValue := data[encName] + + if encType[len(encType)-1:] == "]" { + arrayValue, _ := encValue.([]interface{}) + parsedType := strings.Split(encType, "[")[0] + for _, item := range arrayValue { + if typedData.Types[parsedType] != nil { + mapValue, _ := item.(map[string]interface{}) + mapOutput := typedData.PrettyPrintData(parsedType, mapValue, depth+1) + output.WriteString(mapOutput) + } else { + primitiveOutput := typedData.PrettyPrintPrimitiveValue(encType, encName, encValue, depth) + output.WriteString(primitiveOutput) + } + } + } else if typedData.Types[field["type"]] != nil { + output.WriteString(strings.Repeat("\u00a0", depth*2)) + output.WriteString(fmt.Sprintf("\"%s\": { %s\n", field["name"], encType)) + + mapValue, _ := encValue.(map[string]interface{}) + mapOutput := typedData.PrettyPrintData(field["type"], mapValue, depth+1) + output.WriteString(mapOutput) + + output.Truncate(output.Len() - 2) + output.WriteString(fmt.Sprintf("\n%s},\n", strings.Repeat("\u00a0", depth*2))) + } else { + primitiveOutput := typedData.PrettyPrintPrimitiveValue(encType, encName, encValue, depth) + output.WriteString(primitiveOutput) + } + } + + return output.String() +} + +// PrettyPrintPrimitiveValue generates a formatted output for the +// given primitive value +func (typedData *TypedData) PrettyPrintPrimitiveValue(encType string, encName string, encValue interface{}, depth int) string { + output := bytes.Buffer{} + output.WriteString(strings.Repeat("\u00a0", depth*2)) + output.WriteString(fmt.Sprintf("\"%s\": ", encName)) + + switch encType { + case "address": + stringValue, _ := encValue.(string) + addressValue := common.HexToAddress(stringValue) + output.WriteString(fmt.Sprintf("%s,\n", addressValue.String())) + case "bool": + boolValue, _ := encValue.(bool) + output.WriteString(fmt.Sprintf("%t,\n", boolValue)) + case "bytes", "string": + output.WriteString(fmt.Sprintf("\"%s\",\n", encValue)) + default: + if strings.HasPrefix(encType, "bytes") { + output.WriteString(fmt.Sprintf("\"%s\",\n", encValue)) + } else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { + bigIntValue, _ := encValue.(*big.Int) + output.WriteString(fmt.Sprintf("%d,\n", bigIntValue)) + } + } + return output.String() +} + // Validate checks if the types object is conformant to the specs func (types *Types) Validate() error { for typeKey, typeArr := range *types { From 43d77608879d466f39e3bf9d425da1c1cb939203 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sat, 29 Sep 2018 01:35:27 +0300 Subject: [PATCH 35/84] Named functions and defined a basic EIP191 content type list --- cmd/clef/main.go | 2 - signer/core/api.go | 198 ++++++++++++++++++++++++++++++++++++++++ signer/core/api_test.go | 40 ++++++++ 3 files changed, 238 insertions(+), 2 deletions(-) diff --git a/cmd/clef/main.go b/cmd/clef/main.go index f3464b2ff6a3..1dac85af921f 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -664,8 +664,6 @@ func testExternalUI(api *core.SignerAPI) { checkErr("SignTransaction", err) _, err = api.SignData(ctx, "text/plain", common.MixedcaseAddress{}, common.Hex2Bytes("01020304")) checkErr("SignData", err) - //_, err = api.SignTypedData(ctx, common.MixedcaseAddress{}, core.TypedData{}) - //checkErr("SignTypedData", err) _, err = api.List(ctx) checkErr("List", err) _, err = api.New(ctx) diff --git a/signer/core/api.go b/signer/core/api.go index 49532f6ac43b..2fdd4d542ce9 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -21,8 +21,11 @@ import ( "encoding/json" "errors" "fmt" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto/sha3" "io/ioutil" "math/big" + "mime" "reflect" "github.com/ethereum/go-ethereum/accounts" @@ -175,7 +178,11 @@ type ( SignDataRequest struct { ContentType string `json:"content_type"` Address common.MixedcaseAddress `json:"address"` +<<<<<<< HEAD Rawdata interface{} `json:"raw_data"` +======= + Rawdata hexutil.Bytes `json:"raw_data"` +>>>>>>> 834cf03b0... Named functions and defined a basic EIP191 content type list Message string `json:"message"` Hash hexutil.Bytes `json:"hash"` Meta Metadata `json:"meta"` @@ -514,6 +521,197 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth } +<<<<<<< HEAD +======= +// SignData signs the hash of the provided data, but does so differently +// depending on the content-type specified. +// +// Depending on the content-type, different types of validations will occur. +// +// Note, the produced signature conforms to the secp256k1 curve R, S and V values, +// where the V value will be 27 or 28 for legacy reasons. +func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { + + var req, err = api.determineSignatureFormat(contentType, data) + if err != nil { + return nil, err + } + req.Address = addr + req.Meta = MetadataFromContext(ctx) + + // We make the request prior to looking up if we actually have the account, to prevent + // account-enumeration via the API + res, err := api.UI.ApproveSignData(req) + if err != nil { + return nil, err + } + if !res.Approved { + return nil, ErrRequestDenied + } + // Look up the wallet containing the requested signer + account := accounts.Account{Address: addr.Address()} + wallet, err := api.am.Find(account) + if err != nil { + return nil, err + } + // Sign the data with the wallet + signature, err := wallet.SignHashWithPassphrase(account, res.Password, req.Hash) + if err != nil { + api.UI.ShowError(err.Error()) + return nil, err + } + signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper + return signature, nil +} + +// EcRecover 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 +// the address of: +// hash = keccak256("\x19Ethereum Signed Message:\n"${message length}${message}) +// addr = ecrecover(hash, signature) +// +// Note, the signature must conform to the secp256k1 curve R, S and V values, where +// the V value must be be 27 or 28 for legacy reasons. +// +// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover +func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data, sig hexutil.Bytes) (common.Address, error) { + if len(sig) != 65 { + return common.Address{}, fmt.Errorf("signature must be 65 bytes long") + } + if sig[64] != 27 && sig[64] != 28 { + return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") + } + sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 + hash, _ := SignDataPlain(data) + rpk, err := crypto.SigToPub(hash, sig) + if err != nil { + return common.Address{}, err + } + return crypto.PubkeyToAddress(*rpk), nil +} + +// Determines which signature method should be used based upon the mime type +func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil.Bytes) (*SignDataRequest, error) { + var req *SignDataRequest + mediaType, _, err := mime.ParseMediaType(contentType) + if err != nil { + return nil, err + } + switch mediaType { + case "application/clique": + header := &types.Header{} + if err := rlp.DecodeBytes(data, header); err != nil { + return nil, err + } + sighash, err := SignCliqueHeader(header) + if err != nil { + return nil, err + } + msg := fmt.Sprintf("Clique block %d [0x%x]", header.Number, header.Hash()) + req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + case "application/validator": + // Sign calculates an Ethereum ECDSA signature for: + // keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) + + // In the cases where it matter ensure that the charset is handled. The charset + // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType + // charset, ok := params["charset"] + // As it is now, we accept any charset and just treat it as 'raw'. + + sighash, msg := DataWithValidatorHash(data) + req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + case "data/structured": + // EIP712 typed data + + // Sign calculates an Ethereum ECDSA signature for: + // keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) + + // In the cases where it matter ensure that the charset is handled. The charset + // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType + // charset, ok := params["charset"] + // As it is now, we accept any charset and just treat it as 'raw'. + + sighash, msg := DataStructuredHash(data) + req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + case "data/plain": + // Sign calculates an Ethereum ECDSA signature for: + // keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) + + // In the cases where it matter ensure that the charset is handled. The charset + // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType + // charset, ok := params["charset"] + // As it is now, we accept any charset and just treat it as 'raw'. + + sighash, msg := DataPlainHash(data) + req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + default: + return nil, fmt.Errorf("content type '%s' not implemented for signing", contentType) + } + return req, nil + +} + +// SignCliqueHeader returns the hash which is used as input for the proof-of-authority +// signing. It is the hash of the entire header apart from the 65 byte signature +// contained at the end of the extra data. +// +// The method requires the extra data to be at least 65 bytes -- the original implementation +// in clique.go panics if this is the case, thus it's been reimplemented here to avoid the panic +// and simply return an error instead +func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { + hash := common.Hash{} + if len(header.Extra) < 65 { + return hash.Bytes(), fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) + } + hasher := sha3.NewKeccak256() + rlp.Encode(hasher, []interface{}{ + header.ParentHash, + header.UncleHash, + header.Coinbase, + header.Root, + header.TxHash, + header.ReceiptHash, + header.Bloom, + header.Difficulty, + header.Number, + header.GasLimit, + header.GasUsed, + header.Time, + header.Extra[:len(header.Extra)-65], + header.MixDigest, + header.Nonce, + }) + hasher.Sum(hash[:0]) + return hash.Bytes(), nil +} + +// DataWithValidatorHash signs the given message according to EIP191. +// +// https://github.com/ethereum/EIPs/issues/712 +func DataWithValidatorHash(data []byte) ([]byte, string) { + return nil, "" +} + +// DataStructuredHash signs the given message according to EIP712. +// +// https://github.com/ethereum/EIPs/issues/712 +func DataStructuredHash(data []byte) ([]byte, string) { + return nil, "" +} + +// DataPlainHash is a helper function that calculates a hash for the given message that can be +// safely used to calculate a signature from. +// +// The hash is calculated as +// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). +// +// This gives context to the signed message and prevents signing of transactions. +func DataPlainHash(data []byte) ([]byte, string) { + msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) + return crypto.Keccak256([]byte(msg)), msg +} + +>>>>>>> 834cf03b0... Named functions and defined a basic EIP191 content type list // Export returns encrypted private key associated with the given address in web3 keystore format. func (api *SignerAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) { res, err := api.UI.ApproveExport(&ExportRequest{Address: addr, Meta: MetadataFromContext(ctx)}) diff --git a/signer/core/api_test.go b/signer/core/api_test.go index ff68c0b4e1f0..3bab5d0e979e 100644 --- a/signer/core/api_test.go +++ b/signer/core/api_test.go @@ -245,6 +245,46 @@ func TestNewAcc(t *testing.T) { } } +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" + h, err := api.SignData(context.Background(), "text/plain", 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(), "text/plain", 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(), "text/plain", 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 mkTestTx(from common.MixedcaseAddress) SendTxArgs { to := common.NewMixedcaseAddress(common.HexToAddress("0x1337")) gas := hexutil.Uint64(21000) From 094da11af9c317356145d1ffb789ee3c66b5f94d Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sat, 29 Sep 2018 16:54:07 +0300 Subject: [PATCH 36/84] Written basic content type functions --- signer/core/api.go | 156 ++++++++++++++++++++++++++------------------- 1 file changed, 89 insertions(+), 67 deletions(-) diff --git a/signer/core/api.go b/signer/core/api.go index 2fdd4d542ce9..29c763e20a52 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -111,6 +111,30 @@ type Metadata struct { Origin string `json:"Origin"` } +type SigFormat struct { + Mime string + ByteVersion byte +} + +var ( + ApplicationValidator = SigFormat{ + "application/validator", + 0x00, + } + ApplicationClique = SigFormat{ + "application/clique", + 0x01, + } + DataPlain = SigFormat{ + "data/plain", + 0x45, + } + DataStructured = SigFormat{ + "data/structured", + 0x46, + } +) + // MetadataFromContext extracts Metadata from a given context.Context func MetadataFromContext(ctx context.Context) Metadata { m := Metadata{"NA", "NA", "NA", "", ""} // batman @@ -521,8 +545,6 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth } -<<<<<<< HEAD -======= // SignData signs the hash of the provided data, but does so differently // depending on the content-type specified. // @@ -532,7 +554,7 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth // where the V value will be 27 or 28 for legacy reasons. func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { - var req, err = api.determineSignatureFormat(contentType, data) + var req, err = api.DetermineSignatureFormat(contentType, data) if err != nil { return nil, err } @@ -564,41 +586,16 @@ func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr com return signature, nil } -// EcRecover 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 -// the address of: -// hash = keccak256("\x19Ethereum Signed Message:\n"${message length}${message}) -// addr = ecrecover(hash, signature) -// -// Note, the signature must conform to the secp256k1 curve R, S and V values, where -// the V value must be be 27 or 28 for legacy reasons. -// -// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover -func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data, sig hexutil.Bytes) (common.Address, error) { - if len(sig) != 65 { - return common.Address{}, fmt.Errorf("signature must be 65 bytes long") - } - if sig[64] != 27 && sig[64] != 28 { - return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") - } - sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 - hash, _ := SignDataPlain(data) - rpk, err := crypto.SigToPub(hash, sig) - if err != nil { - return common.Address{}, err - } - return crypto.PubkeyToAddress(*rpk), nil -} - // Determines which signature method should be used based upon the mime type -func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil.Bytes) (*SignDataRequest, error) { +func (api *SignerAPI) DetermineSignatureFormat(contentType string, data hexutil.Bytes) (*SignDataRequest, error) { var req *SignDataRequest mediaType, _, err := mime.ParseMediaType(contentType) if err != nil { return nil, err } switch mediaType { - case "application/clique": + case ApplicationClique.Mime: + // Clique is the Ethereum PoA standard header := &types.Header{} if err := rlp.DecodeBytes(data, header); err != nil { return nil, err @@ -609,40 +606,26 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil. } msg := fmt.Sprintf("Clique block %d [0x%x]", header.Number, header.Hash()) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - case "application/validator": - // Sign calculates an Ethereum ECDSA signature for: - // keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) - - // In the cases where it matter ensure that the charset is handled. The charset - // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType - // charset, ok := params["charset"] - // As it is now, we accept any charset and just treat it as 'raw'. + case ApplicationValidator.Mime: + // Data with an intended validator - sighash, msg := DataWithValidatorHash(data) + sighash, msg := SignDataWithValidator(data) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - case "data/structured": - // EIP712 typed data + case DataStructured.Mime: + // Typed data according to EIP712 - // Sign calculates an Ethereum ECDSA signature for: - // keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) - - // In the cases where it matter ensure that the charset is handled. The charset - // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType - // charset, ok := params["charset"] - // As it is now, we accept any charset and just treat it as 'raw'. - - sighash, msg := DataStructuredHash(data) + sighash, msg := SignDataStructured(data) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - case "data/plain": + case DataPlain.Mime: // Sign calculates an Ethereum ECDSA signature for: - // keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) + // keccack256("\x19${byte version}Ethereum Signed Message:\n" + len(message) + message)) - // In the cases where it matter ensure that the charset is handled. The charset + // In the cases where it matters ensure that the charset is handled. The charset // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType // charset, ok := params["charset"] // As it is now, we accept any charset and just treat it as 'raw'. - sighash, msg := DataPlainHash(data) + sighash, msg := SignDataPlain(data) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} default: return nil, fmt.Errorf("content type '%s' not implemented for signing", contentType) @@ -685,33 +668,72 @@ func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { return hash.Bytes(), nil } -// DataWithValidatorHash signs the given message according to EIP191. -// -// https://github.com/ethereum/EIPs/issues/712 -func DataWithValidatorHash(data []byte) ([]byte, string) { - return nil, "" +// DataWithValidatorHash signs the given message which can be further recovered +// with the given validator. +func SignDataWithValidator(data []byte) ([]byte, string) { + msg := "TODO" + return crypto.Keccak256([]byte(msg)), msg } // DataStructuredHash signs the given message according to EIP712. // // https://github.com/ethereum/EIPs/issues/712 -func DataStructuredHash(data []byte) ([]byte, string) { - return nil, "" +func SignDataStructured(data []byte) ([]byte, string) { + msg := "TODO" + return crypto.Keccak256([]byte(msg)), msg } -// DataPlainHash is a helper function that calculates a hash for the given message that can be +// SignDataPlain is a helper function that calculates a hash for the given message that can be // safely used to calculate a signature from. // // The hash is calculated as -// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). +// keccak256("\x19${byte version}Ethereum Signed Message:\n"${message length}${message}). // // This gives context to the signed message and prevents signing of transactions. -func DataPlainHash(data []byte) ([]byte, string) { - msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) +func SignDataPlain(data []byte) ([]byte, string) { + msg := fmt.Sprintf("\x19\\x%xEthereum Signed Message:\n%d%s", DataPlain.ByteVersion, len(data), data) return crypto.Keccak256([]byte(msg)), msg } ->>>>>>> 834cf03b0... Named functions and defined a basic EIP191 content type list +// Determines the content type and then recovers the address associated with the given sig +func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data, sig hexutil.Bytes) (common.Address, error) { + fmt.Println("Effing Muffins") + mediaType, _, err := mime.ParseMediaType(contentType) + if err != nil { + return common.Address{}, err + } + switch mediaType { + case DataPlain.Mime: + // 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 + // the address of: + // hash = keccak256("\x19${byte version}Ethereum Signed Message:\n"${message length}${message}) + // addr = ecrecover(hash, signature) + // + // Note, the signature must conform to the secp256k1 curve R, S and V values, where + // the V value must be be 27 or 28 for legacy reasons. + // + // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover + + if len(sig) != 65 { + return common.Address{}, fmt.Errorf("signature must be 65 bytes long") + } + if sig[64] != 27 && sig[64] != 28 { + return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") + } + sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 + hash, _ := SignDataPlain(data) + rpk, err := crypto.SigToPub(hash, sig) + if err != nil { + return common.Address{}, err + } + return crypto.PubkeyToAddress(*rpk), nil + default: + return common.Address{}, fmt.Errorf("content type '%s' not implemented for ecRecover", contentType) + } +} + // Export returns encrypted private key associated with the given address in web3 keystore format. func (api *SignerAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) { res, err := api.UI.ApproveExport(&ExportRequest{Address: addr, Meta: MetadataFromContext(ctx)}) From d21570d4cc62551542696c3822b0766ef44c2e0c Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sat, 29 Sep 2018 17:16:34 +0300 Subject: [PATCH 37/84] Added ecRecover method in the clef api --- signer/core/api.go | 7 ++----- signer/core/auditlog.go | 6 +++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/signer/core/api.go b/signer/core/api.go index 29c763e20a52..0e24445c0c19 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -50,11 +50,9 @@ type ExternalAPI interface { // SignTransaction request to sign the specified transaction SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) // SignData - request to sign the given data (plus prefix) - SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error) - // SignTypedData - request to sign the given structured data (plus prefix) - SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) + SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) // EcRecover - recover public key from given message and signature - EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) + EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) // Export - request to export an account Export(ctx context.Context, addr common.Address) (json.RawMessage, error) // Import - request to import an account @@ -697,7 +695,6 @@ func SignDataPlain(data []byte) ([]byte, string) { // Determines the content type and then recovers the address associated with the given sig func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data, sig hexutil.Bytes) (common.Address, error) { - fmt.Println("Effing Muffins") mediaType, _, err := mime.ParseMediaType(contentType) if err != nil { return common.Address{}, err diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go index 578e5ddcb54a..0762415f5421 100644 --- a/signer/core/auditlog.go +++ b/signer/core/auditlog.go @@ -78,10 +78,10 @@ func (l *AuditLogger) SignTypedData(ctx context.Context, addr common.MixedcaseAd return b, e } -func (l *AuditLogger) EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { +func (l *AuditLogger) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { l.log.Info("EcRecover", "type", "request", "metadata", MetadataFromContext(ctx).String(), - "data", common.Bytes2Hex(data), "sig", common.Bytes2Hex(sig)) - b, e := l.api.EcRecover(ctx, data, sig) + "data", common.Bytes2Hex(data), "sig", common.Bytes2Hex(sig), "content-type", contentType) + b, e := l.api.EcRecover(ctx, contentType, data, sig) l.log.Info("EcRecover", "type", "response", "address", b.String(), "error", e) return b, e } From 678ee915f28ce1500fa978ac7ba94598b4bf6cd7 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sat, 29 Sep 2018 17:52:05 +0300 Subject: [PATCH 38/84] Updated the extapi changelog and addded indications in the README --- cmd/clef/README.md | 6 +++++- cmd/clef/extapi_changelog.md | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/clef/README.md b/cmd/clef/README.md index 205b183e5000..be0e10747e89 100644 --- a/cmd/clef/README.md +++ b/cmd/clef/README.md @@ -350,13 +350,15 @@ Bash example: {"jsonrpc":"2.0","id":67,"result":{"raw":"0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","tx":{"nonce":"0x0","gasPrice":"0x1","gas":"0x333","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0","value":"0x0","input":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012","v":"0x26","r":"0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e","s":"0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","hash":"0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e"}}} ``` + ### account_signData #### Sign data Signs a chunk of data and returns the calculated signature. #### Arguments - - content type [string]: type of signed data + + - content type [string]: type of data to sign - `text/validator`: hex data with custom validator defined in a contract - `application/clique`: [clique](https://github.com/ethereum/EIPs/issues/225) headers - `text/plain`: simple hex data validated by `account_ecRecover` @@ -489,9 +491,11 @@ Response ### account_ecRecover #### Sign data + Derive the address from the account that was used to sign data with content type `text/plain` and the signature. #### Arguments + - content type [string]: type of signed data - data [data]: data that was signed - signature [data]: the signature to verify diff --git a/cmd/clef/extapi_changelog.md b/cmd/clef/extapi_changelog.md index 7adc33960590..4b9ff6192ff3 100644 --- a/cmd/clef/extapi_changelog.md +++ b/cmd/clef/extapi_changelog.md @@ -2,13 +2,14 @@ #### 5.0.0 -* The external `account_EcRecover`-method was reimplemented. +* The external `account_EcRecover`-method was added again. * The external method `account_sign(address, data)` was replaced with `account_signData(contentType, address, data)`. The addition of `contentType` makes it possible to use the method for different types of objects, such as: * signing data with an intended validator (not yet implemented) * signing clique headers, * signing plain personal messages, * The external method `account_signTypedData` implements [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md) and makes it possible to sign typed data. + * signing structured data adhering to [EIP-712](https://eips.ethereum.org/EIPS/eip-712) (not yet implemented) #### 4.0.0 From 854a94efd4b0d785e038becd6622729f1ee47ad9 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Wed, 3 Oct 2018 00:16:27 +0100 Subject: [PATCH 39/84] Added tests for 0x45 --- signer/core/api.go | 8 ++++---- signer/core/api_test.go | 40 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/signer/core/api.go b/signer/core/api.go index 0e24445c0c19..49748a690fb7 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -110,8 +110,8 @@ type Metadata struct { } type SigFormat struct { - Mime string - ByteVersion byte + Mime string + ByteVersion byte } var ( @@ -666,14 +666,14 @@ func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { return hash.Bytes(), nil } -// DataWithValidatorHash signs the given message which can be further recovered +// SignDataWithValidator signs the given message which can be further recovered // with the given validator. func SignDataWithValidator(data []byte) ([]byte, string) { msg := "TODO" return crypto.Keccak256([]byte(msg)), msg } -// DataStructuredHash signs the given message according to EIP712. +// SignDataStructured signs the given message according to EIP712. // // https://github.com/ethereum/EIPs/issues/712 func SignDataStructured(data []byte) ([]byte, string) { diff --git a/signer/core/api_test.go b/signer/core/api_test.go index 3bab5d0e979e..bf49dfaa946c 100644 --- a/signer/core/api_test.go +++ b/signer/core/api_test.go @@ -245,7 +245,21 @@ func TestNewAcc(t *testing.T) { } } -func TestSignData(t *testing.T) { +func signApplicationValidator(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) { api, control := setup(t) //Create two accounts createAccount(control, api, t) @@ -259,7 +273,7 @@ func TestSignData(t *testing.T) { control <- "Y" control <- "wrongpassword" - h, err := api.SignData(context.Background(), "text/plain", a, []byte("EHLO world")) + h, err := api.SignData(context.Background(), DataPlain.Mime, a, []byte("EHLO world")) if h != nil { t.Errorf("Expected nil-data, got %x", h) } @@ -267,7 +281,7 @@ func TestSignData(t *testing.T) { t.Errorf("Expected ErrLocked! %v", err) } control <- "No way" - h, err = api.SignData(context.Background(), "text/plain", a, []byte("EHLO world")) + h, err = api.SignData(context.Background(), DataPlain.Mime, a, []byte("EHLO world")) if h != nil { t.Errorf("Expected nil-data, got %x", h) } @@ -276,7 +290,7 @@ func TestSignData(t *testing.T) { } control <- "Y" control <- "a_long_password" - h, err = api.SignData(context.Background(), "text/plain", a, []byte("EHLO world")) + h, err = api.SignData(context.Background(), DataPlain.Mime, a, []byte("EHLO world")) if err != nil { t.Fatal(err) } @@ -285,6 +299,24 @@ func TestSignData(t *testing.T) { } } +func signDataStructured(t *testing.T) { + // TODO +} + +func TestSignData(t *testing.T) { + // application/validator or `0x00` + signApplicationValidator(t) + + // application/clique or `0x01` + signApplicationClique(t) + + // data/plain or `0x45` + signDataPlain(t) + + // data/structured `0x46` + signDataStructured(t) +} + func mkTestTx(from common.MixedcaseAddress) SendTxArgs { to := common.NewMixedcaseAddress(common.HexToAddress("0x1337")) gas := hexutil.Uint64(21000) From adccccbb88d0632b8211d85f9c62651e1b3cc2d8 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Wed, 10 Oct 2018 14:53:53 -0700 Subject: [PATCH 40/84] Implementing UnmarshalJSON() for TypedData --- cmd/clef/audit.log | 11 ++++ cmd/clef/main.go | 24 +++---- interfaces.go | 5 ++ signer/core/api.go | 2 + signer/core/api_layout.md | 134 ++++++++++++++++++++++++++++++++++++++ signer/core/apiv2.go | 57 ++++++++++++++++ signer/core/auditlog.go | 8 +-- 7 files changed, 226 insertions(+), 15 deletions(-) create mode 100644 cmd/clef/audit.log create mode 100644 signer/core/api_layout.md create mode 100644 signer/core/apiv2.go diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log new file mode 100644 index 000000000000..66302cac9786 --- /dev/null +++ b/cmd/clef/audit.log @@ -0,0 +1,11 @@ +t=2018-10-10T14:45:26-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-10T14:45:37-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59942\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data={PrimaryType:0x4d61696c} +t=2018-10-10T14:45:37-0700 lvl=info msg=SignStructuredData api=signer type=response data= error=nil +t=2018-10-10T14:45:55-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-10T14:45:55-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59956\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data={PrimaryType:0x4d61696c} +t=2018-10-10T14:46:18-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-10T14:46:50-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59967\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data={PrimaryType:0x4d61696c} +t=2018-10-10T14:51:32-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-10T14:51:59-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60000\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data="{Hash:[226 140 80 200 149 243 254 73 125 193 200 45 83 189 176 154 4 204 167 86 220 130 208 11 170 238 116 129 150 85 62 148]}" +t=2018-10-10T14:52:51-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-10T14:52:56-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60017\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data="{Hash:[226 140 80 200 149 243 254 73 125 193 200 45 83 189 176 154 4 204 167 86 220 130 208 11 170 238 116 129 150 85 62 148]}" diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 1dac85af921f..7e5ae5686812 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -330,9 +330,10 @@ func initialize(c *cli.Context) error { // If using the stdioui, we can't do the 'confirm'-flow fmt.Fprintf(logOutput, legalWarning) } else { - if !confirm(legalWarning) { - return fmt.Errorf("aborted by user") - } + // Temporarily disabled while in development + //if !confirm(legalWarning) { + // return fmt.Errorf("aborted by user") + //} } log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int(logLevelFlag.Name)), log.StreamHandler(logOutput, log.TerminalFormat(true)))) @@ -555,14 +556,15 @@ 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 { - resp, err := ui.OnInputRequired(core.UserInputRequest{ - Title: "Master Password", - Prompt: "Please enter the password to decrypt the master seed", - IsPassword: true}) - if err != nil { - return nil, err - } - password = resp.Text + // Temporarily disabled while in development + //resp, err := ui.OnInputRequired(core.UserInputRequest{ + // Title: "Master Password", + // Prompt: "Please enter the password to decrypt the master seed", + // IsPassword: true}) + //if err != nil { + // return nil, err + //} + //password = resp.Text } else { password = getPassPhrase("Decrypt master seed of clef", false) } diff --git a/interfaces.go b/interfaces.go index 1ff31f96b6a6..840e916aaf72 100644 --- a/interfaces.go +++ b/interfaces.go @@ -209,3 +209,8 @@ type GasEstimator interface { type PendingStateEventer interface { SubscribePendingTransactions(ctx context.Context, ch chan<- *types.Transaction) (Subscription, error) } + +// TypedData is the standard for EIP-712 signed data. +type TypedData struct { + Hash *common.Hash `json:"hash"` +} diff --git a/signer/core/api.go b/signer/core/api.go index 49748a690fb7..61d1406a50be 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -51,6 +51,8 @@ type ExternalAPI interface { SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) // SignData - request to sign the given data (plus prefix) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) + // SignStructuredData - request to sign the given structured data (plus prefix) + SignStructuredData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) // EcRecover - recover public key from given message and signature EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) // Export - request to export an account diff --git a/signer/core/api_layout.md b/signer/core/api_layout.md new file mode 100644 index 000000000000..bcb8c4cc5bb2 --- /dev/null +++ b/signer/core/api_layout.md @@ -0,0 +1,134 @@ +# Specs +`encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01" β€– domainSeparator β€– hashStruct(message)` +- data adheres to π•Š, a structure defined in the rigorous eip-712 +- `\x01` is needed to comply with EIP-191 +- `domainSeparator` and `hashStruct` are defined below + +## A) domainSeparator +`domainSeparator = hashStruct(eip712Domain)` +
+
+Struct named `EIP712Domain` with one or more of the below fields: + +- `string name` +- `string version` +- `uint256 chainId`, as per EIP-155 +- `address verifyingContract` +- `bytes32 salt` + +## B) hashStruct +`hashStruct(s : π•Š) = keccak256(typeHash β€– encodeData(s))` +
+`typeHash = keccak256(encodeType(typeOf(s)))` + +### i) encodeType +- `name β€– "(" β€– member₁ β€– "," β€– memberβ‚‚ β€– "," β€– … β€– memberβ‚™ ")"` +- each member is written as `type β€– " " β€– name` +- encodings cascade down and are sorted by name + +### ii) encodeData +- `enc(value₁) β€– enc(valueβ‚‚) β€– … β€– enc(valueβ‚™)` +- each encoded member is 32-byte long + + #### a) atomic + + - `boolean` => `uint256` + - `address` => `uint160` + - `uint` => sign-extended `uint256` in big endian order + - `bytes1:31` => `bytes32` + + #### b) dynamic + + - `bytes` => `keccak256(bytes)` + - `string` => `keccak256(string)` + + #### c) referenced + + - `array` => `keccak256(encodeData(array))` + - `struct` => `rec(keccak256(hashStruct(struct)))` + +## C) Example +### Query +```json +{ + "jsonrpc": "2.0", + "method": "account_signStructuredData", + "params": [ + "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + { + "types": { + "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" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": 1, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } + } + ], + "id": 1 +} +``` + +### Response +```json +{ + "id":1, + "jsonrpc": "2.0", + "result": "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c" +} +``` \ No newline at end of file diff --git a/signer/core/apiv2.go b/signer/core/apiv2.go new file mode 100644 index 000000000000..0ef25cb8d204 --- /dev/null +++ b/signer/core/apiv2.go @@ -0,0 +1,57 @@ +package core + +import ( + "context" + "encoding/json" + "fmt" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +//type TypedData struct { + //Types map[string] interface{} `json:"types"` + //PrimaryType *big.Int `json:"primaryType"` + //Domain EIP712Domain `json:"domain"` + //Message map[string] interface{} `json:"message"` +//} + +//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"` +//} + +// TypedData represents a request to create a new filter. +// Same as ethereum.FilterQuery but with UnmarshalJSON() method. +type TypedData ethereum.TypedData + +// Typed data according to EIP712 +// +// If the format "\x19\x46" β€– domainSeparator β€– hashStruct(message)` is not respected, +// an error is returned +func (api *SignerAPI) SignStructuredData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) { + fmt.Println("data", data) + fmt.Println("data.Hash", data.Hash) + return common.Hex2Bytes("0xdeadbeef"), nil +} + +// UnmarshalJSON sets *args fields with given data. +func (args *TypedData) UnmarshalJSON(data []byte) error { + type input struct { + Hash *common.Hash `json:"hash"` + } + + var raw input + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + if raw.Hash != nil { + args.Hash = raw.Hash + } + + return nil +} \ No newline at end of file diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go index 0762415f5421..7c6cf1f51670 100644 --- a/signer/core/auditlog.go +++ b/signer/core/auditlog.go @@ -70,11 +70,11 @@ func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr com return b, e } -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(), +func (l *AuditLogger) SignStructuredData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) { + l.log.Info("SignStructuredData", "type", "request", "metadata", MetadataFromContext(ctx).String(), "addr", addr.String(), "data", data) - b, e := l.api.SignTypedData(ctx, addr, data) - l.log.Info("SignTypedData", "type", "response", "data", common.Bytes2Hex(b), "error", e) + b, e := l.api.SignStructuredData(ctx, addr, TypedData{}) + l.log.Info("SignStructuredData", "type", "response", "data", common.Bytes2Hex(b), "error", e) return b, e } From ed5f7f3b14f71d438790e100d8ab7b3bf98dd052 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Thu, 11 Oct 2018 13:05:54 -0700 Subject: [PATCH 41/84] Working on TypedData --- cmd/clef/audit.log | 11 -------- signer/core/api.go | 41 ++++++++++++++++++++++------- signer/core/{apiv2.go => apiv2.goz} | 2 +- signer/core/auditlog.go | 6 ++--- 4 files changed, 35 insertions(+), 25 deletions(-) delete mode 100644 cmd/clef/audit.log rename signer/core/{apiv2.go => apiv2.goz} (91%) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log deleted file mode 100644 index 66302cac9786..000000000000 --- a/cmd/clef/audit.log +++ /dev/null @@ -1,11 +0,0 @@ -t=2018-10-10T14:45:26-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-10T14:45:37-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59942\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data={PrimaryType:0x4d61696c} -t=2018-10-10T14:45:37-0700 lvl=info msg=SignStructuredData api=signer type=response data= error=nil -t=2018-10-10T14:45:55-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-10T14:45:55-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59956\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data={PrimaryType:0x4d61696c} -t=2018-10-10T14:46:18-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-10T14:46:50-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59967\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data={PrimaryType:0x4d61696c} -t=2018-10-10T14:51:32-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-10T14:51:59-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60000\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data="{Hash:[226 140 80 200 149 243 254 73 125 193 200 45 83 189 176 154 4 204 167 86 220 130 208 11 170 238 116 129 150 85 62 148]}" -t=2018-10-10T14:52:51-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-10T14:52:56-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60017\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data="{Hash:[226 140 80 200 149 243 254 73 125 193 200 45 83 189 176 154 4 204 167 86 220 130 208 11 170 238 116 129 150 85 62 148]}" diff --git a/signer/core/api.go b/signer/core/api.go index 61d1406a50be..befa3e3f383f 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -21,6 +21,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto/sha3" "io/ioutil" @@ -52,7 +53,7 @@ type ExternalAPI interface { // SignData - request to sign the given data (plus prefix) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) // SignStructuredData - request to sign the given structured data (plus prefix) - SignStructuredData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) + SignStructuredData(ctx context.Context, data TypedData) (hexutil.Bytes, error) // EcRecover - recover public key from given message and signature EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) // Export - request to export an account @@ -611,11 +612,6 @@ func (api *SignerAPI) DetermineSignatureFormat(contentType string, data hexutil. sighash, msg := SignDataWithValidator(data) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - case DataStructured.Mime: - // Typed data according to EIP712 - - sighash, msg := SignDataStructured(data) - req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} case DataPlain.Mime: // Sign calculates an Ethereum ECDSA signature for: // keccack256("\x19${byte version}Ethereum Signed Message:\n" + len(message) + message)) @@ -675,12 +671,37 @@ func SignDataWithValidator(data []byte) ([]byte, string) { return crypto.Keccak256([]byte(msg)), msg } -// SignDataStructured signs the given message according to EIP712. +// TypedData represents a request to create a new filter. +type TypedData ethereum.TypedData + +// SignStructuredData signs the given message according to EIP712. // // https://github.com/ethereum/EIPs/issues/712 -func SignDataStructured(data []byte) ([]byte, string) { - msg := "TODO" - return crypto.Keccak256([]byte(msg)), msg +// +// If the format "\x19\x46" β€– domainSeparator β€– hashStruct(message)` is not respected, +// an error is returned +func (api *SignerAPI) SignStructuredData(ctx context.Context, data TypedData) (hexutil.Bytes, error) { + fmt.Println("data", data) + fmt.Println("data.Hash", data.Hash) + return common.Hex2Bytes("0xdeadbeef"), nil +} + +// UnmarshalJSON sets *args fields with given data. +func (args *TypedData) UnmarshalJSON(data []byte) error { + type input struct { + Hash *common.Hash `json:"hash"` + } + + var raw input + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + if raw.Hash != nil { + args.Hash = raw.Hash + } + + return nil } // SignDataPlain is a helper function that calculates a hash for the given message that can be diff --git a/signer/core/apiv2.go b/signer/core/apiv2.goz similarity index 91% rename from signer/core/apiv2.go rename to signer/core/apiv2.goz index 0ef25cb8d204..87716a8736df 100644 --- a/signer/core/apiv2.go +++ b/signer/core/apiv2.goz @@ -32,7 +32,7 @@ type TypedData ethereum.TypedData // // If the format "\x19\x46" β€– domainSeparator β€– hashStruct(message)` is not respected, // an error is returned -func (api *SignerAPI) SignStructuredData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) { +func (api *SignerAPI) SignStructuredData(ctx context.Context, data TypedData) (hexutil.Bytes, error) { fmt.Println("data", data) fmt.Println("data.Hash", data.Hash) return common.Hex2Bytes("0xdeadbeef"), nil diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go index 7c6cf1f51670..99a7a5297144 100644 --- a/signer/core/auditlog.go +++ b/signer/core/auditlog.go @@ -70,10 +70,10 @@ func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr com return b, e } -func (l *AuditLogger) SignStructuredData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) { +func (l *AuditLogger) SignStructuredData(ctx context.Context, data TypedData) (hexutil.Bytes, error) { l.log.Info("SignStructuredData", "type", "request", "metadata", MetadataFromContext(ctx).String(), - "addr", addr.String(), "data", data) - b, e := l.api.SignStructuredData(ctx, addr, TypedData{}) + "addr", "data", data) + b, e := l.api.SignStructuredData(ctx, TypedData{}) l.log.Info("SignStructuredData", "type", "response", "data", common.Bytes2Hex(b), "error", e) return b, e } From 1478b8894737844a7b5fdad9d556cae57ee0c1a6 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sat, 13 Oct 2018 05:04:06 -0700 Subject: [PATCH 42/84] Solved the auditlog issue --- cmd/clef/audit.log | 1 + interfaces.go | 7 +---- signer/core/api.go | 34 ------------------------ signer/core/apiv2.go | 56 ++++++++++++++++++++++++++++++++++++++++ signer/core/apiv2.goz | 57 ----------------------------------------- signer/core/auditlog.go | 2 +- 6 files changed, 59 insertions(+), 98 deletions(-) create mode 100644 cmd/clef/audit.log create mode 100644 signer/core/apiv2.go delete mode 100644 signer/core/apiv2.goz diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log new file mode 100644 index 000000000000..c5b542d9b0ba --- /dev/null +++ b/cmd/clef/audit.log @@ -0,0 +1 @@ +t=2018-10-13T05:03:16-0700 lvl=info msg=Configured api=signer audit log=audit.log diff --git a/interfaces.go b/interfaces.go index 840e916aaf72..a7de78249164 100644 --- a/interfaces.go +++ b/interfaces.go @@ -208,9 +208,4 @@ type GasEstimator interface { // pending state. type PendingStateEventer interface { SubscribePendingTransactions(ctx context.Context, ch chan<- *types.Transaction) (Subscription, error) -} - -// TypedData is the standard for EIP-712 signed data. -type TypedData struct { - Hash *common.Hash `json:"hash"` -} +} \ No newline at end of file diff --git a/signer/core/api.go b/signer/core/api.go index befa3e3f383f..9c08149f0e8f 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -21,7 +21,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto/sha3" "io/ioutil" @@ -671,39 +670,6 @@ func SignDataWithValidator(data []byte) ([]byte, string) { return crypto.Keccak256([]byte(msg)), msg } -// TypedData represents a request to create a new filter. -type TypedData ethereum.TypedData - -// SignStructuredData signs the given message according to EIP712. -// -// https://github.com/ethereum/EIPs/issues/712 -// -// If the format "\x19\x46" β€– domainSeparator β€– hashStruct(message)` is not respected, -// an error is returned -func (api *SignerAPI) SignStructuredData(ctx context.Context, data TypedData) (hexutil.Bytes, error) { - fmt.Println("data", data) - fmt.Println("data.Hash", data.Hash) - return common.Hex2Bytes("0xdeadbeef"), nil -} - -// UnmarshalJSON sets *args fields with given data. -func (args *TypedData) UnmarshalJSON(data []byte) error { - type input struct { - Hash *common.Hash `json:"hash"` - } - - var raw input - if err := json.Unmarshal(data, &raw); err != nil { - return err - } - - if raw.Hash != nil { - args.Hash = raw.Hash - } - - return nil -} - // SignDataPlain is a helper function that calculates a hash for the given message that can be // safely used to calculate a signature from. // diff --git a/signer/core/apiv2.go b/signer/core/apiv2.go new file mode 100644 index 000000000000..4f3e737300c3 --- /dev/null +++ b/signer/core/apiv2.go @@ -0,0 +1,56 @@ +package core + +import ( + "context" + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "math/big" +) + +type TypedData struct { + Types map[string] interface{} `json:"types"` + PrimaryType string `json:"primaryType"` + Domain EIP712Domain `json:"domain"` + Message map[string] interface{} `json:"message"` +} + +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"` +} + +// Typed data according to EIP712 +// +// If the format "\x19\x46" β€– domainSeparator β€– hashStruct(message)` is not respected, +// an error is returned +func (api *SignerAPI) SignStructuredData(ctx context.Context, data TypedData) (hexutil.Bytes, error) { + fmt.Println("data", data) + fmt.Println("data.PrimaryType", data.PrimaryType) + return common.Hex2Bytes("0xdeadbeef"), nil +} + +// TypedData represents a request to create a new filter. +// Same as ethereum.FilterQuery but with UnmarshalJSON() method. +//type TypedData ethereum.TypedData + +// UnmarshalJSON sets *args fields with given data. +//func (args *TypedData) UnmarshalJSON(data []byte) error { +// type input struct { +// Hash *common.Hash `json:"hash"` +// } +// +// var raw input +// if err := json.Unmarshal(data, &raw); err != nil { +// return err +// } +// +// if raw.Hash != nil { +// args.Hash = raw.Hash +// } +// +// return nil +//} \ No newline at end of file diff --git a/signer/core/apiv2.goz b/signer/core/apiv2.goz deleted file mode 100644 index 87716a8736df..000000000000 --- a/signer/core/apiv2.goz +++ /dev/null @@ -1,57 +0,0 @@ -package core - -import ( - "context" - "encoding/json" - "fmt" - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" -) - -//type TypedData struct { - //Types map[string] interface{} `json:"types"` - //PrimaryType *big.Int `json:"primaryType"` - //Domain EIP712Domain `json:"domain"` - //Message map[string] interface{} `json:"message"` -//} - -//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"` -//} - -// TypedData represents a request to create a new filter. -// Same as ethereum.FilterQuery but with UnmarshalJSON() method. -type TypedData ethereum.TypedData - -// Typed data according to EIP712 -// -// If the format "\x19\x46" β€– domainSeparator β€– hashStruct(message)` is not respected, -// an error is returned -func (api *SignerAPI) SignStructuredData(ctx context.Context, data TypedData) (hexutil.Bytes, error) { - fmt.Println("data", data) - fmt.Println("data.Hash", data.Hash) - return common.Hex2Bytes("0xdeadbeef"), nil -} - -// UnmarshalJSON sets *args fields with given data. -func (args *TypedData) UnmarshalJSON(data []byte) error { - type input struct { - Hash *common.Hash `json:"hash"` - } - - var raw input - if err := json.Unmarshal(data, &raw); err != nil { - return err - } - - if raw.Hash != nil { - args.Hash = raw.Hash - } - - return nil -} \ No newline at end of file diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go index 99a7a5297144..e9d9da3dbea0 100644 --- a/signer/core/auditlog.go +++ b/signer/core/auditlog.go @@ -73,7 +73,7 @@ func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr com func (l *AuditLogger) SignStructuredData(ctx context.Context, data TypedData) (hexutil.Bytes, error) { l.log.Info("SignStructuredData", "type", "request", "metadata", MetadataFromContext(ctx).String(), "addr", "data", data) - b, e := l.api.SignStructuredData(ctx, TypedData{}) + b, e := l.api.SignStructuredData(ctx, data) l.log.Info("SignStructuredData", "type", "response", "data", common.Bytes2Hex(b), "error", e) return b, e } From bd38e0ca9d4495bb9718d6a08234f858b0e36063 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sun, 14 Oct 2018 19:57:28 +0100 Subject: [PATCH 43/84] Changed method to signTypedData --- cmd/clef/audit.log | 15 +++++ signer/core/api.go | 122 +++++++++++++++++++++----------------- signer/core/api_layout.md | 4 +- signer/core/apiv2.go | 32 ++-------- signer/core/auditlog.go | 10 ++-- 5 files changed, 96 insertions(+), 87 deletions(-) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log index c5b542d9b0ba..d339f7e75c37 100644 --- a/cmd/clef/audit.log +++ b/cmd/clef/audit.log @@ -1 +1,16 @@ t=2018-10-13T05:03:16-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-13T05:04:21-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-13T05:04:35-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61564\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr=data LOG15_ERROR= LOG15_ERROR="Normalized odd number of arguments by adding nil" +t=2018-10-13T05:04:35-0700 lvl=info msg=SignStructuredData api=signer type=response data= error=nil +t=2018-10-13T05:07:31-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-13T05:35:59-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-13T05:37:45-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-13T05:42:26-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-13T05:43:13-0700 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61669\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[type:string name:version] 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:{neg:false abs:[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-13T05:43:13-0700 lvl=info msg=SignTypedData api=signer type=response data= error=nil +t=2018-10-13T05:43:50-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-13T05:44:06-0700 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61680\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Mail:[map[name:from type:Person] map[type:Person name:to] 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]] Person:[map[type:string name:name] map[name:wallet type:address]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:{neg:false abs:[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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-13T05:44:06-0700 lvl=info msg=SignTypedData api=signer type=response data= error=nil +t=2018-10-13T05:46:53-0700 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-13T05:46:58-0700 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61694\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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-13T05:46:58-0700 lvl=info msg=SignTypedData api=signer type=response data= error=nil diff --git a/signer/core/api.go b/signer/core/api.go index 9c08149f0e8f..a70e667ab64d 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -52,7 +52,7 @@ type ExternalAPI interface { // SignData - request to sign the given data (plus prefix) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) // SignStructuredData - request to sign the given structured data (plus prefix) - SignStructuredData(ctx context.Context, 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(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) // Export - request to export an account @@ -117,21 +117,21 @@ type SigFormat struct { } var ( - ApplicationValidator = SigFormat{ - "application/validator", + TextPlain = SigFormat{ + "text/plain", 0x00, } - ApplicationClique = SigFormat{ - "application/clique", + TextValidator = SigFormat{ + "text/validator", 0x01, } - DataPlain = SigFormat{ - "data/plain", + DataTyped = SigFormat{ + "data/typed", 0x45, } - DataStructured = SigFormat{ - "data/structured", - 0x46, + ApplicationClique = SigFormat{ + "application/clique", + 0x90, } ) @@ -554,7 +554,7 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth // where the V value will be 27 or 28 for legacy reasons. func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { - var req, err = api.DetermineSignatureFormat(contentType, data) + var req, err = api.determineSignatureFormat(contentType, data) if err != nil { return nil, err } @@ -587,40 +587,56 @@ func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr com } // Determines which signature method should be used based upon the mime type -func (api *SignerAPI) DetermineSignatureFormat(contentType string, data hexutil.Bytes) (*SignDataRequest, error) { +func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil.Bytes) (*SignDataRequest, error) { var req *SignDataRequest mediaType, _, err := mime.ParseMediaType(contentType) if err != nil { return nil, err } switch mediaType { - case ApplicationClique.Mime: - // Clique is the Ethereum PoA standard - header := &types.Header{} - if err := rlp.DecodeBytes(data, header); err != nil { - return nil, err - } - sighash, err := SignCliqueHeader(header) - if err != nil { - return nil, err - } - msg := fmt.Sprintf("Clique block %d [0x%x]", header.Number, header.Hash()) - req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - case ApplicationValidator.Mime: + case TextValidator.Mime: // Data with an intended validator - sighash, msg := SignDataWithValidator(data) + sighash, msg := signTextWithValidator(data) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - case DataPlain.Mime: + case TextPlain.Mime: // Sign calculates an Ethereum ECDSA signature for: - // keccack256("\x19${byte version}Ethereum Signed Message:\n" + len(message) + message)) + // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") // In the cases where it matters ensure that the charset is handled. The charset // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType // charset, ok := params["charset"] // As it is now, we accept any charset and just treat it as 'raw'. - sighash, msg := SignDataPlain(data) + sighash, msg := signTextPlain(data) + req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + //case DataTyped.Mime: + // // Typed data according to EIP712: + // // + // // hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}") + // fmt.Println("Did we get here, chief? #1") + // typedData := TypedData{} + // if err := rlp.DecodeBytes(data, typedData); err != nil { + // return nil, err + // } + // fmt.Println("Did we get here, chief? #2") + // sighash, err := signTypedData(context.Background(), typedData) + // if err != nil { + // return nil, err + // } + // msg := fmt.Sprintf("Typed data domain %s", typedData.Domain) + // req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + case ApplicationClique.Mime: + // Clique is the Ethereum PoA standard + header := &types.Header{} + if err := rlp.DecodeBytes(data, header); err != nil { + return nil, err + } + sighash, err := signCliqueHeader(header) + if err != nil { + return nil, err + } + msg := fmt.Sprintf("Clique block %d [0x%x]", header.Number, header.Hash()) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} default: return nil, fmt.Errorf("content type '%s' not implemented for signing", contentType) @@ -629,6 +645,25 @@ func (api *SignerAPI) DetermineSignatureFormat(contentType string, data hexutil. } +// signTextPlain is a helper function that calculates a hash for the given message that can be +// safely used to calculate a signature from. +// +// The hash is calculated as +// keccak256("\x19${byteVersion}Ethereum Signed Message:\n"${message length}${message}). +// +// This gives context to the signed message and prevents signing of transactions. +func signTextPlain(data []byte) ([]byte, string) { + msg := fmt.Sprintf("\x19\\x%xEthereum Signed Message:\n%d%s", TextPlain.ByteVersion, len(data), data) + return crypto.Keccak256([]byte(msg)), msg +} + +// signTextWithValidator signs the given message which can be further recovered +// with the given validator. +func signTextWithValidator(data []byte) ([]byte, string) { + msg := "TODO" + return crypto.Keccak256([]byte(msg)), msg +} + // SignCliqueHeader returns the hash which is used as input for the proof-of-authority // signing. It is the hash of the entire header apart from the 65 byte signature // contained at the end of the extra data. @@ -636,7 +671,7 @@ func (api *SignerAPI) DetermineSignatureFormat(contentType string, data hexutil. // The method requires the extra data to be at least 65 bytes -- the original implementation // in clique.go panics if this is the case, thus it's been reimplemented here to avoid the panic // and simply return an error instead -func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { +func signCliqueHeader(header *types.Header) (hexutil.Bytes, error) { hash := common.Hash{} if len(header.Extra) < 65 { return hash.Bytes(), fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) @@ -663,38 +698,19 @@ func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { return hash.Bytes(), nil } -// SignDataWithValidator signs the given message which can be further recovered -// with the given validator. -func SignDataWithValidator(data []byte) ([]byte, string) { - msg := "TODO" - return crypto.Keccak256([]byte(msg)), msg -} - -// SignDataPlain is a helper function that calculates a hash for the given message that can be -// safely used to calculate a signature from. -// -// The hash is calculated as -// keccak256("\x19${byte version}Ethereum Signed Message:\n"${message length}${message}). -// -// This gives context to the signed message and prevents signing of transactions. -func SignDataPlain(data []byte) ([]byte, string) { - msg := fmt.Sprintf("\x19\\x%xEthereum Signed Message:\n%d%s", DataPlain.ByteVersion, len(data), data) - return crypto.Keccak256([]byte(msg)), msg -} - // Determines the content type and then recovers the address associated with the given sig -func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data, sig hexutil.Bytes) (common.Address, error) { +func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { mediaType, _, err := mime.ParseMediaType(contentType) if err != nil { return common.Address{}, err } switch mediaType { - case DataPlain.Mime: + case TextPlain.Mime: // 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 // the address of: - // hash = keccak256("\x19${byte version}Ethereum Signed Message:\n"${message length}${message}) + // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") // addr = ecrecover(hash, signature) // // Note, the signature must conform to the secp256k1 curve R, S and V values, where @@ -709,7 +725,7 @@ func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data, s return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") } sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 - hash, _ := SignDataPlain(data) + hash, _ := signTextPlain(data) rpk, err := crypto.SigToPub(hash, sig) if err != nil { return common.Address{}, err diff --git a/signer/core/api_layout.md b/signer/core/api_layout.md index bcb8c4cc5bb2..289f711eb9f1 100644 --- a/signer/core/api_layout.md +++ b/signer/core/api_layout.md @@ -1,7 +1,7 @@ # Specs -`encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01" β€– domainSeparator β€– hashStruct(message)` +`encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x45" β€– domainSeparator β€– hashStruct(message)` - data adheres to π•Š, a structure defined in the rigorous eip-712 -- `\x01` is needed to comply with EIP-191 +- `\x45` is needed to comply with EIP-191 - `domainSeparator` and `hashStruct` are defined below ## A) domainSeparator diff --git a/signer/core/apiv2.go b/signer/core/apiv2.go index 4f3e737300c3..3485bcdf6436 100644 --- a/signer/core/apiv2.go +++ b/signer/core/apiv2.go @@ -25,32 +25,10 @@ type EIP712Domain struct { // Typed data according to EIP712 // -// If the format "\x19\x46" β€– domainSeparator β€– hashStruct(message)` is not respected, -// an error is returned -func (api *SignerAPI) SignStructuredData(ctx context.Context, data TypedData) (hexutil.Bytes, error) { - fmt.Println("data", data) - fmt.Println("data.PrimaryType", data.PrimaryType) +// hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}") +func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) { + fmt.Println("addr", addr) + //fmt.Println("data", data) + fmt.Println("data.Domain", data.Domain) return common.Hex2Bytes("0xdeadbeef"), nil } - -// TypedData represents a request to create a new filter. -// Same as ethereum.FilterQuery but with UnmarshalJSON() method. -//type TypedData ethereum.TypedData - -// UnmarshalJSON sets *args fields with given data. -//func (args *TypedData) UnmarshalJSON(data []byte) error { -// type input struct { -// Hash *common.Hash `json:"hash"` -// } -// -// var raw input -// if err := json.Unmarshal(data, &raw); err != nil { -// return err -// } -// -// if raw.Hash != nil { -// args.Hash = raw.Hash -// } -// -// return nil -//} \ No newline at end of file diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go index e9d9da3dbea0..0762415f5421 100644 --- a/signer/core/auditlog.go +++ b/signer/core/auditlog.go @@ -70,11 +70,11 @@ func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr com return b, e } -func (l *AuditLogger) SignStructuredData(ctx context.Context, data TypedData) (hexutil.Bytes, error) { - l.log.Info("SignStructuredData", "type", "request", "metadata", MetadataFromContext(ctx).String(), - "addr", "data", data) - b, e := l.api.SignStructuredData(ctx, data) - l.log.Info("SignStructuredData", "type", "response", "data", common.Bytes2Hex(b), "error", e) +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(), + "addr", addr.String(), "data", data) + b, e := l.api.SignTypedData(ctx, addr, data) + l.log.Info("SignTypedData", "type", "response", "data", common.Bytes2Hex(b), "error", e) return b, e } From 190530ea77c509068fdcccf0d2afc7a33003dc77 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Mon, 15 Oct 2018 03:36:07 +0100 Subject: [PATCH 44/84] Changed mimes and implemented the 'encodeType' function for EIP-712 --- cmd/clef/audit.log | 48 +++++++ cmd/clef/extapi_changelog.md | 5 +- cmd/clef/main.go | 4 + interfaces.go | 2 +- signer/core/api.go | 32 ++--- signer/core/api_layout.md | 10 +- signer/core/apiv2.go | 251 +++++++++++++++++++++++++++++++++-- 7 files changed, 307 insertions(+), 45 deletions(-) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log index d339f7e75c37..9b0ef72b850f 100644 --- a/cmd/clef/audit.log +++ b/cmd/clef/audit.log @@ -14,3 +14,51 @@ t=2018-10-13T05:44:06-0700 lvl=info msg=SignTypedData api=signer type=response d t=2018-10-13T05:46:53-0700 lvl=info msg=Configured api=signer audit log=audit.log t=2018-10-13T05:46:58-0700 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61694\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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-13T05:46:58-0700 lvl=info msg=SignTypedData api=signer type=response data= error=nil +t=2018-10-14T21:00:04+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T21:00:06+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56799\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-14T21:00:06+0100 lvl=info msg=SignTypedData api=signer type=response data= error="Couldn't decode types of EIP712Domain" +t=2018-10-14T21:00:50+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T21:01:02+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56810\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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:{neg:false abs:[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-14T21:01:02+0100 lvl=info msg=SignTypedData api=signer type=response data= error="Couldn't decode types of EIP712Domain" +t=2018-10-14T21:01:39+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T21:01:53+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56823\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-14T21:01:53+0100 lvl=info msg=SignTypedData api=signer type=response data= error="Couldn't decode types of EIP712Domain" +t=2018-10-14T21:11:30+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T21:11:33+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56899\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] map[name:verifyingContract type:address]] Person:[map[type:string name:name] 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:{neg:false abs:[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-14T21:11:33+0100 lvl=info msg=SignTypedData api=signer type=response data= error="Couldn't decode types of EIP712Domain" +t=2018-10-14T21:15:28+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T21:15:34+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56968\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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-14T21:15:34+0100 lvl=info msg=SignTypedData api=signer type=response data= error="Couldn't decode types of EIP712Domain" +t=2018-10-14T21:17:53+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T21:17:57+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56991\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:Person name:to] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:{neg:false abs:[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-14T21:17:57+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-14T21:18:32+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T21:18:36+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57006\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:Person name:from] map[name:to type:Person] map[type:string name:contents]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:{neg:false abs:[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-14T21:18:36+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-14T21:18:59+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T21:19:00+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57017\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] EIP712Domain:[map[name:name type:string] map[type:string name:version] map[name:chainId type:uint256] map[name:verifyingContract type:address]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:{neg:false abs:[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-14T21:19:00+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-14T21:30:19+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T21:30:29+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57188\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-14T21:30:29+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-14T21:51:40+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T21:51:42+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57392\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] EIP712Domain:[map[name:name type:string] map[type:string name:version] 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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-14T21:51:42+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-14T22:16:07+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T22:16:14+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57602\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]]}" +t=2018-10-14T22:16:14+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-14T22:18:31+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T22:18:35+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57653\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-14T22:18:35+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-14T22:20:47+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-14T22:20:50+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57669\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]] EIP712Domain:[map[name:name type:string] map[type:string name:version] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet 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-14T22:20:50+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-15T03:11:09+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-15T03:11:17+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50605\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-15T03:11:17+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-15T03:12:01+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-15T03:12:04+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50618\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-15T03:12:04+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-15T03:14:48+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-15T03:15:25+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50635\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-15T03:15:25+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil diff --git a/cmd/clef/extapi_changelog.md b/cmd/clef/extapi_changelog.md index 4b9ff6192ff3..a9ab42e5b521 100644 --- a/cmd/clef/extapi_changelog.md +++ b/cmd/clef/extapi_changelog.md @@ -2,14 +2,13 @@ #### 5.0.0 -* The external `account_EcRecover`-method was added again. +* The external `account_EcRecover`-method was reimplemented. * The external method `account_sign(address, data)` was replaced with `account_signData(contentType, address, data)`. The addition of `contentType` makes it possible to use the method for different types of objects, such as: * signing data with an intended validator (not yet implemented) * signing clique headers, * signing plain personal messages, -* The external method `account_signTypedData` implements [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md) and makes it possible to sign typed data. - * signing structured data adhering to [EIP-712](https://eips.ethereum.org/EIPS/eip-712) (not yet implemented) +* The external method `account_signTypedData` [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md) and makes it possible to sign typed data. #### 4.0.0 diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 7e5ae5686812..648dd59306bf 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -26,6 +26,8 @@ import ( "encoding/hex" "encoding/json" "fmt" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" "io" "io/ioutil" "math/big" @@ -666,6 +668,8 @@ func testExternalUI(api *core.SignerAPI) { checkErr("SignTransaction", err) _, err = api.SignData(ctx, "text/plain", common.MixedcaseAddress{}, common.Hex2Bytes("01020304")) checkErr("SignData", err) + _, err = api.SignTypedData(ctx, common.MixedcaseAddress{}, core.TypedData{}) + checkErr("SignTypedData", err) _, err = api.List(ctx) checkErr("List", err) _, err = api.New(ctx) diff --git a/interfaces.go b/interfaces.go index a7de78249164..1ff31f96b6a6 100644 --- a/interfaces.go +++ b/interfaces.go @@ -208,4 +208,4 @@ type GasEstimator interface { // pending state. type PendingStateEventer interface { SubscribePendingTransactions(ctx context.Context, ch chan<- *types.Transaction) (Subscription, error) -} \ No newline at end of file +} diff --git a/signer/core/api.go b/signer/core/api.go index a70e667ab64d..11e52144e29d 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -117,21 +117,21 @@ type SigFormat struct { } var ( - TextPlain = SigFormat{ - "text/plain", - 0x00, - } TextValidator = SigFormat{ "text/validator", - 0x01, + 0x00, } DataTyped = SigFormat{ "data/typed", - 0x45, + 0x01, } ApplicationClique = SigFormat{ "application/clique", - 0x90, + 0x02, + } + TextPlain = SigFormat{ + "text/plain", + 0x45, } ) @@ -610,22 +610,6 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil. sighash, msg := signTextPlain(data) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - //case DataTyped.Mime: - // // Typed data according to EIP712: - // // - // // hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}") - // fmt.Println("Did we get here, chief? #1") - // typedData := TypedData{} - // if err := rlp.DecodeBytes(data, typedData); err != nil { - // return nil, err - // } - // fmt.Println("Did we get here, chief? #2") - // sighash, err := signTypedData(context.Background(), typedData) - // if err != nil { - // return nil, err - // } - // msg := fmt.Sprintf("Typed data domain %s", typedData.Domain) - // req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} case ApplicationClique.Mime: // Clique is the Ethereum PoA standard header := &types.Header{} @@ -664,7 +648,7 @@ func signTextWithValidator(data []byte) ([]byte, string) { return crypto.Keccak256([]byte(msg)), msg } -// SignCliqueHeader returns the hash which is used as input for the proof-of-authority +// signCliqueHeader returns the hash which is used as input for the proof-of-authority // signing. It is the hash of the entire header apart from the 65 byte signature // contained at the end of the extra data. // diff --git a/signer/core/api_layout.md b/signer/core/api_layout.md index 289f711eb9f1..1a251fe9eef6 100644 --- a/signer/core/api_layout.md +++ b/signer/core/api_layout.md @@ -1,7 +1,7 @@ # Specs -`encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x45" β€– domainSeparator β€– hashStruct(message)` +`encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01" β€– domainSeparator β€– hashStruct(message)` - data adheres to π•Š, a structure defined in the rigorous eip-712 -- `\x45` is needed to comply with EIP-191 +- `\x00` is needed to comply with EIP-191 - `domainSeparator` and `hashStruct` are defined below ## A) domainSeparator @@ -26,6 +26,8 @@ Struct named `EIP712Domain` with one or more of the below fields: - each member is written as `type β€– " " β€– name` - encodings cascade down and are sorted by name +Example: `Mail(Person from,Person to,string contents)Person(string name,address wallet)` + ### ii) encodeData - `enc(value₁) β€– enc(valueβ‚‚) β€– … β€– enc(valueβ‚™)` - each encoded member is 32-byte long @@ -52,7 +54,7 @@ Struct named `EIP712Domain` with one or more of the below fields: ```json { "jsonrpc": "2.0", - "method": "account_signStructuredData", + "method": "account_signTypedData", "params": [ "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", { @@ -131,4 +133,4 @@ Struct named `EIP712Domain` with one or more of the below fields: "jsonrpc": "2.0", "result": "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c" } -``` \ No newline at end of file +``` diff --git a/signer/core/apiv2.go b/signer/core/apiv2.go index 3485bcdf6436..8c87ebd8448a 100644 --- a/signer/core/apiv2.go +++ b/signer/core/apiv2.go @@ -1,34 +1,259 @@ package core import ( + "bytes" "context" + "encoding/hex" "fmt" "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 map[string] interface{} `json:"types"` - PrimaryType string `json:"primaryType"` - Domain EIP712Domain `json:"domain"` - Message map[string] interface{} `json:"message"` + Types EIP712Types `json:"types"` + PrimaryType string `json:"primaryType"` + Domain EIP712Domain `json:"domain"` + Message EIP712Message `json:"message"` +} + +type EIP712Types map[string][]map[string]string + +type EIP712TypePriority struct { + Type string + Value uint } 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 common.Address `json:"verifyingContract"` + Salt hexutil.Bytes `json:"salt"` } +type EIP712Message map[string]interface{} + // Typed data according to EIP712 // // hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}") func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) { - fmt.Println("addr", addr) - //fmt.Println("data", data) - fmt.Println("data.Domain", data.Domain) - return common.Hex2Bytes("0xdeadbeef"), nil + if err := data.Domain.IsValid(); err != nil { + return nil, err + } + if data.PrimaryType == "" { + return nil, fmt.Errorf("primary type undefined") + } + + domainTypes := EIP712Types{ + "EIP712Domain": data.Types["EIP712Domain"], + } + domainSeparator, err := hashStruct(domainTypes, data.Domain.Values(), "") + 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 + } + + fmt.Println("domainSeparator", domainSeparator.String()) + fmt.Println("typedDataHash", typedDataHash.String()) + return common.FromHex("0xdeadbeef"), nil +} + +// `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") + } + } + + typeEncoding, err := encodeType(types, primaryType) + if err != nil { + return common.Hash{}, err + } + typeHash := hex.EncodeToString(crypto.Keccak256([]byte(typeEncoding))) + + dataEncoding, err := encodeData(message) + if err != nil { + return common.Hash{}, err + } + dataHash := hex.EncodeToString(crypto.Keccak256([]byte(dataEncoding))) + + var buffer bytes.Buffer + buffer.WriteString(typeHash) + buffer.WriteString(dataHash) + hash := common.BytesToHash(crypto.Keccak256(buffer.Bytes())) + + return hash, nil +} + +// encodeType transforms the given types into an encoding of the form +// `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) { + 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 { + for _, elem := range arr { + if elem == val { + return true + } + } + return false + } + + for typeKey, typeArr := range types { + var typeValArr []string + + for _, typeObj := range typeArr { + typeVal := typeObj["type"] + if typeKey == typeVal { + return "", fmt.Errorf("type %s cannot reference itself", typeVal) + } + + firstChar := []rune(typeVal)[0] + if unicode.IsUpper(firstChar) { + if types[typeVal] != nil { + if !visited(typeValArr, typeVal) { + typeValArr = append(typeValArr, typeVal) + update(typeKey, typeVal) + } + } else { + return "", fmt.Errorf("referenced type %s is undefined", typeVal) + } + } else { + if !types.IsStandardType(typeVal) { + if types[typeVal] != nil { + return "", fmt.Errorf("Custom type %s must be capitalized", typeVal) + } else { + return "", fmt.Errorf("Unknown type %s", typeVal) + } + } + } + } + + typeValArr = []string{} + } + + sortedPriorities := types.SortByPriorityAndName(priorities) + var buffer bytes.Buffer + for _, priority := range sortedPriorities { + typeKey := priority.Type + typeArr := types[typeKey] + + buffer.WriteString(typeKey) + buffer.WriteString("(") + + for _, typeObj := range typeArr { + buffer.WriteString(typeObj["type"]) + buffer.WriteString(" ") + buffer.WriteString(typeObj["name"]) + buffer.WriteString(",") + } + + buffer.Truncate(buffer.Len() - 1) + buffer.WriteString(")") + } + + return buffer.String(), nil +} + +func encodeData(values EIP712Message) (string, error) { + return "", nil +} + +// Checks if the given type is a standard type accepted by EIP-712 +func (types *EIP712Types) IsStandardType(typeStr string) bool { + standardTypes := []string{ + "array", + "address", + "boolean", + "bytes", + "string", + "struct", + "uint", + } + for _, val := range standardTypes { + if strings.HasPrefix(typeStr, val) { + return true + } + } + return false +} + +// 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 { + 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 + }) + + for _, priority := range priorities { + fmt.Printf("%s, Value %d\n", priority.Type, priority.Value) + } + fmt.Printf("\n") + + return priorities +} + +// Check 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") + } + + if domain.Name == "" && domain.Version == "" && len(domain.VerifyingContract) == 0 && len(domain.Salt) == 0 { + return fmt.Errorf("domain undefined") + } + + return nil +} + +// Helper function to return the values of a domain in the form of a golang map +func (domain *EIP712Domain) Values() map[string]interface{} { + return map[string]interface{}{ + "name": domain.Name, + "version": domain.Version, + "chainId": domain.Name, + "verifyingContract": domain.VerifyingContract, + "salt": domain.Salt, + } } From 99de6ffc4cdc45ee142802ea0ab424abc5c7cb04 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Thu, 18 Oct 2018 09:16:43 +0100 Subject: [PATCH 45/84] Polished docstrings, ran goimports and swapped fmt.Errorf with errors.New where possible --- signer/core/api_test.go | 32 +- signer/core/apiv2.go | 259 ----------- signer/core/signed_data.go | 852 +++++++++---------------------------- 3 files changed, 218 insertions(+), 925 deletions(-) delete mode 100644 signer/core/apiv2.go diff --git a/signer/core/api_test.go b/signer/core/api_test.go index bf49dfaa946c..f1695feec935 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` - signApplicationClique(t) + // data/structured `0x01` + signTypedData(t) - // data/plain or `0x45` - signDataPlain(t) + // application/clique or `0x02` + signApplicationClique(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/apiv2.go deleted file mode 100644 index 8c87ebd8448a..000000000000 --- a/signer/core/apiv2.go +++ /dev/null @@ -1,259 +0,0 @@ -package core - -import ( - "bytes" - "context" - "encoding/hex" - "fmt" - "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"` -} - -type EIP712Types map[string][]map[string]string - -type EIP712TypePriority struct { - Type string - Value uint -} - -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"` -} - -type EIP712Message map[string]interface{} - -// Typed data according to EIP712 -// -// 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") - } - - domainTypes := EIP712Types{ - "EIP712Domain": data.Types["EIP712Domain"], - } - domainSeparator, err := hashStruct(domainTypes, data.Domain.Values(), "") - 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 - } - - fmt.Println("domainSeparator", domainSeparator.String()) - fmt.Println("typedDataHash", typedDataHash.String()) - return common.FromHex("0xdeadbeef"), nil -} - -// `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") - } - } - - typeEncoding, err := encodeType(types, primaryType) - if err != nil { - return common.Hash{}, err - } - typeHash := hex.EncodeToString(crypto.Keccak256([]byte(typeEncoding))) - - dataEncoding, err := encodeData(message) - if err != nil { - return common.Hash{}, err - } - dataHash := hex.EncodeToString(crypto.Keccak256([]byte(dataEncoding))) - - var buffer bytes.Buffer - buffer.WriteString(typeHash) - buffer.WriteString(dataHash) - hash := common.BytesToHash(crypto.Keccak256(buffer.Bytes())) - - return hash, nil -} - -// encodeType transforms the given types into an encoding of the form -// `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) { - 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 { - for _, elem := range arr { - if elem == val { - return true - } - } - return false - } - - for typeKey, typeArr := range types { - var typeValArr []string - - for _, typeObj := range typeArr { - typeVal := typeObj["type"] - if typeKey == typeVal { - return "", fmt.Errorf("type %s cannot reference itself", typeVal) - } - - firstChar := []rune(typeVal)[0] - if unicode.IsUpper(firstChar) { - if types[typeVal] != nil { - if !visited(typeValArr, typeVal) { - typeValArr = append(typeValArr, typeVal) - update(typeKey, typeVal) - } - } else { - return "", fmt.Errorf("referenced type %s is undefined", typeVal) - } - } else { - if !types.IsStandardType(typeVal) { - if types[typeVal] != nil { - return "", fmt.Errorf("Custom type %s must be capitalized", typeVal) - } else { - return "", fmt.Errorf("Unknown type %s", typeVal) - } - } - } - } - - typeValArr = []string{} - } - - sortedPriorities := types.SortByPriorityAndName(priorities) - var buffer bytes.Buffer - for _, priority := range sortedPriorities { - typeKey := priority.Type - typeArr := types[typeKey] - - buffer.WriteString(typeKey) - buffer.WriteString("(") - - for _, typeObj := range typeArr { - buffer.WriteString(typeObj["type"]) - buffer.WriteString(" ") - buffer.WriteString(typeObj["name"]) - buffer.WriteString(",") - } - - buffer.Truncate(buffer.Len() - 1) - buffer.WriteString(")") - } - - return buffer.String(), nil -} - -func encodeData(values EIP712Message) (string, error) { - return "", nil -} - -// Checks if the given type is a standard type accepted by EIP-712 -func (types *EIP712Types) IsStandardType(typeStr string) bool { - standardTypes := []string{ - "array", - "address", - "boolean", - "bytes", - "string", - "struct", - "uint", - } - for _, val := range standardTypes { - if strings.HasPrefix(typeStr, val) { - return true - } - } - return false -} - -// 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 { - 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 - }) - - for _, priority := range priorities { - fmt.Printf("%s, Value %d\n", priority.Type, priority.Value) - } - fmt.Printf("\n") - - return priorities -} - -// Check 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") - } - - if domain.Name == "" && domain.Version == "" && len(domain.VerifyingContract) == 0 && len(domain.Salt) == 0 { - return fmt.Errorf("domain undefined") - } - - return nil -} - -// Helper function to return the values of a domain in the form of a golang map -func (domain *EIP712Domain) Values() map[string]interface{} { - return map[string]interface{}{ - "name": domain.Name, - "version": domain.Version, - "chainId": domain.Name, - "verifyingContract": domain.VerifyingContract, - "salt": domain.Salt, - } -} diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 39e78ea46b97..79e49989ac42 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -1,760 +1,318 @@ -// 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" "errors" "fmt" "math/big" - "mime" + "math/rand" "reflect" - "regexp" "sort" - "strconv" "strings" + "time" "unicode" - "github.com/ethereum/go-ethereum/accounts" + "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/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/crypto/sha3" - "github.com/ethereum/go-ethereum/rlp" ) -type SigFormat struct { - Mime string - ByteVersion byte -} - -var ( - TextValidator = SigFormat{ - "text/validator", - 0x00, - } - DataTyped = SigFormat{ - "data/typed", - 0x01, - } - ApplicationClique = SigFormat{ - "application/clique", - 0x02, - } - TextPlain = SigFormat{ - "text/plain", - 0x45, - } -) - -type ValidatorData struct { - Address common.Address - Message hexutil.Bytes -} - type TypedData struct { - Types Types `json:"types"` - PrimaryType string `json:"primaryType"` - Domain TypedDataDomain `json:"domain"` - Message TypedDataMessage `json:"message"` + Types map[string]EIP712Type `json:"types"` + PrimaryType string `json:"primaryType"` + Domain EIP712Domain `json:"domain"` + Message EIP712Message `json:"message"` } -type Type []map[string]string - -type Types map[string]Type +type EIP712Type []map[string]string -type TypePriority struct { +type EIP712TypePriority struct { Type string Value uint } -type TypedDataMessage = map[string]interface{} +type EIP712Data = map[string]interface{} -type TypedDataDomain struct { - Name string `json:"name"` - Version string `json:"version"` - ChainId *big.Int `json:"chainId"` - VerifyingContract string `json:"verifyingContract"` - Salt string `json:"salt"` +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"` } -var typedDataRegexp = regexp.MustCompile(`^((address|bool|bytes|string)|((bytes)([1-9]|[1-2][0-9]|3[0-2]))|((int|uint)(8|16|32|64|128|256)))(\[])?$`) - -// Sign receives a request and produces a signature +type EIP712Message map[string]interface{} -// Note, the produced signature conforms to the secp256k1 curve R, S and V values, -// where the V value will be 27 or 28 for legacy reasons. -func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, req *SignDataRequest) (hexutil.Bytes, error) { - req.Address = addr - req.Meta = MetadataFromContext(ctx) - - // We make the request prior to looking up if we actually have the account, to prevent - // account-enumeration via the API - res, err := api.UI.ApproveSignData(req) - if err != nil { - return nil, err - } - if !res.Approved { - return nil, ErrRequestDenied - } - // Look up the wallet containing the requested signer - account := accounts.Account{Address: addr.Address()} - wallet, err := api.am.Find(account) - if err != nil { +// 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 } - // Sign the data with the wallet - signature, err := wallet.SignHashWithPassphrase(account, res.Password, req.Hash) - if err != nil { - return nil, err + if data.PrimaryType == "" { + return nil, errors.New("primary type undefined") } - signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper - return signature, nil -} -// SignData signs the hash of the provided data, but does so differently -// depending on the content-type specified. -// -// Different types of validation occur. -func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error) { - var req, err = api.determineSignatureFormat(contentType, addr, data) - if err != nil { - return nil, err + domainTypes := map[string]EIP712Type{ + "EIP712Domain": data.Types["EIP712Domain"], } + domainSeparator := hashStruct(domainTypes, data.PrimaryType, data.Domain.Values(), 0) + //if err != nil { + // return nil, err + //} + delete(data.Types, "EIP712Domain") + typedDataHash := hashStruct(data.Types, data.PrimaryType, data.Message, 0) + //if err != nil { + // return nil, err + //} - signature, err := api.Sign(ctx, addr, req) - if err != nil { - api.UI.ShowError(err.Error()) - return nil, err - } - - return signature, nil + fmt.Println("domainSeparator", domainSeparator.String()) + fmt.Println("typedDataHash", typedDataHash.String()) + return common.FromHex("0xdeadbeef"), nil } -// Determines which signature method should be used based upon the mime type -// In the cases where it matters ensure that the charset is handled. The charset -// resides in the 'params' returned as the second returnvalue from mime.ParseMediaType -// charset, ok := params["charset"] -// As it is now, we accept any charset and just treat it as 'raw'. -func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.MixedcaseAddress, data interface{}) (*SignDataRequest, error) { - var req *SignDataRequest - mediaType, _, err := mime.ParseMediaType(contentType) - if err != nil { - return nil, err - } +// hashStruct generates the following encoding for the given domain and message: +// `encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01" β€– domainSeparator β€– hashStruct(message)` +func hashStruct(types map[string]EIP712Type, key string, data EIP712Data, depth int) common.Hash { + helpers.PrintJson("hashStruct", map[string]interface{}{ + "depth": depth, + }) - switch mediaType { - case TextValidator.Mime: - // Data with an intended validator - validatorData, err := UnmarshalValidatorData(data) - if err != nil { - return nil, err - } - sighash, msg := SignTextValidator(validatorData) - req = &SignDataRequest{ContentType: mediaType, Rawdata: validatorData, Message: msg, Hash: sighash} - case ApplicationClique.Mime: - // Clique is the Ethereum PoA standard - cliqueData, err := hexutil.Decode(data.(string)) - if err != nil { - return nil, err - } - header := &types.Header{} - if err := rlp.DecodeBytes(cliqueData, header); err != nil { - return nil, err - } - sighash, err := SignCliqueHeader(header) - if err != nil { - return nil, err - } - msg := fmt.Sprintf("clique block %d [0x%x]", header.Number, header.Hash()) - req = &SignDataRequest{ContentType: mediaType, Rawdata: cliqueData, Message: msg, Hash: sighash} - case TextPlain.Mime: - // Calculates an Ethereum ECDSA signature for: - // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") - plainData, err := hexutil.Decode(data.(string)) - if err != nil { - return nil, err - } - sighash, msg := SignTextPlain(plainData) - req = &SignDataRequest{ContentType: mediaType, Rawdata: plainData, Message: msg, Hash: sighash} - default: - return nil, fmt.Errorf("content type '%s' not implemented for signing", contentType) - } - return req, nil + typeEncoding := encodeType(types) + typeHash := hex.EncodeToString(crypto.Keccak256([]byte(typeEncoding))) -} + dataEncoding := encodeData(types, key, data, depth) + dataHash := hex.EncodeToString(crypto.Keccak256([]byte(dataEncoding))) -// SignTextWithValidator signs the given message which can be further recovered -// with the given validator. -// hash = keccak256("\x19\x00"${address}${data}). -func SignTextValidator(validatorData ValidatorData) (hexutil.Bytes, string) { - msg := fmt.Sprintf("\x19\x00%s%s", string(validatorData.Address.Bytes()), string(validatorData.Message)) - fmt.Printf("SignTextValidator:%s\n", msg) - return crypto.Keccak256([]byte(msg)), msg -} + var buffer bytes.Buffer + buffer.WriteString(typeHash) + buffer.WriteString(dataHash) + hash := common.BytesToHash(crypto.Keccak256(buffer.Bytes())) -// SignCliqueHeader returns the hash which is used as input for the proof-of-authority -// signing. It is the hash of the entire header apart from the 65 byte signature -// contained at the end of the extra data. -// -// The method requires the extra data to be at least 65 bytes -- the original implementation -// in clique.go panics if this is the case, thus it's been reimplemented here to avoid the panic -// and simply return an error instead -func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { - hash := common.Hash{} - if len(header.Extra) < 65 { - return hash.Bytes(), fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) + if depth == 0 { + fmt.Printf("typeEncoding %s\n", typeEncoding) + fmt.Printf("dataEncoding %s\n", dataEncoding) } - hasher := sha3.NewKeccak256() - rlp.Encode(hasher, []interface{}{ - header.ParentHash, - header.UncleHash, - header.Coinbase, - header.Root, - header.TxHash, - header.ReceiptHash, - header.Bloom, - header.Difficulty, - header.Number, - header.GasLimit, - header.GasUsed, - header.Time, - header.Extra[:len(header.Extra)-65], - header.MixDigest, - header.Nonce, - }) - hasher.Sum(hash[:0]) - return hash.Bytes(), nil + return hash } -// 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 -// signing of transactions. -// hash = keccak256("\x19$Ethereum Signed Message:\n"${message length}${message}). -func SignTextPlain(data hexutil.Bytes) (hexutil.Bytes, string) { - // The letter `E` is \x45 in hex, retrofitting - // https://github.com/ethereum/go-ethereum/pull/2940/commits - msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data)) - return crypto.Keccak256([]byte(msg)), msg -} +// 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 map[string]EIP712Type) string { + helpers.PrintJson("hashStruct", map[string]interface{}{ + "types": types, + }) -// 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) { - if err := typedData.Validate(); err != nil { - return nil, err - } - domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) - if err != nil { - return nil, err - } - typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) - if err != nil { - return nil, err - } - sighash := crypto.Keccak256([]byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))) - output := typedData.PrettyPrint() - req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: typedData.Map(), Message: output, Hash: sighash} - signature, err := api.Sign(ctx, addr, req) - if err != nil { - api.UI.ShowError(err.Error()) - return nil, err + var priorities = make(map[string]uint) + for key := range types { + priorities[key] = 0 } - return signature, nil -} -// HashStruct generates a keccak256 hash of the encoding of the provided data -func (typedData *TypedData) HashStruct(primaryType string, data TypedDataMessage) (hexutil.Bytes, error) { - encodedData, err := typedData.EncodeData(primaryType, data, 1) - if err != nil { - return nil, err + // 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]++ + } + } } - return crypto.Keccak256(encodedData), nil -} -// 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 == str { + // Checks if referenced type has already been visited to optimise algo + visited := func(arr []string, val string) bool { + for _, elem := range arr { + if elem == val { return true } } return false } - 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) + for typeKey, typeArr := range types { + var typeValArr []string + + for _, typeObj := range typeArr { + typeVal := typeObj["type"] + if typeKey == typeVal { + panic(fmt.Errorf("type %s cannot reference itself", typeVal)) + } + + firstChar := []rune(typeVal)[0] + if unicode.IsUpper(firstChar) { + if types[typeVal] != nil { + if !visited(typeValArr, typeVal) { + typeValArr = append(typeValArr, typeVal) + update(typeKey, typeVal) + } + } else { + panic(fmt.Errorf("referenced type %s is undefined", typeVal)) + } + } else { + if !isStandardType(typeVal) { + if types[typeVal] != nil { + panic(fmt.Errorf("Custom type %s must be capitalized", typeVal)) + } else { + panic(fmt.Errorf("Unknown type %s", typeVal)) + } + } } } + + typeValArr = []string{} } - return found -} -// 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) hexutil.Bytes { - // Get dependencies primary first, then alphabetical - deps := typedData.Dependencies(primaryType, []string{}) - slicedDeps := deps[1:] - sort.Strings(slicedDeps) - deps = append([]string{primaryType}, slicedDeps...) - - // Format as a string with fields + sortedPriorities := sortByPriorityAndName(priorities) var buffer bytes.Buffer - for _, dep := range deps { - buffer.WriteString(dep) + for _, priority := range sortedPriorities { + typeKey := priority.Type + typeArr := types[typeKey] + + buffer.WriteString(typeKey) buffer.WriteString("(") - for _, obj := range typedData.Types[dep] { - buffer.WriteString(obj["type"]) + + for _, typeObj := range typeArr { + buffer.WriteString(typeObj["type"]) buffer.WriteString(" ") - buffer.WriteString(obj["name"]) + buffer.WriteString(typeObj["name"]) buffer.WriteString(",") } + buffer.Truncate(buffer.Len() - 1) buffer.WriteString(")") } - return buffer.Bytes() -} -func (typedData *TypedData) TypeHash(primaryType string) hexutil.Bytes { - return crypto.Keccak256(typedData.EncodeType(primaryType)) + return buffer.String() } -// EncodeData generates the following encoding: +// encodeData generates the following encoding: // `enc(value₁) β€– enc(valueβ‚‚) β€– … β€– enc(valueβ‚™)` // // each encoded member is 32-byte long -func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}, depth int) (hexutil.Bytes, error) { - buffer := bytes.Buffer{} - - // Verify extra data - if len(typedData.Types[primaryType]) < len(data) { - return nil, errors.New("there is extra data provided in the message") - } - - // Add typehash - buffer.Write(typedData.TypeHash(primaryType)) - - // Add field contents. Structs and arrays have special handlers. - for _, field := range typedData.Types[primaryType] { - encType := field["type"] - encValue := data[field["name"]] - if encType[len(encType)-1:] == "]" { - arrayValue, ok := encValue.([]interface{}) - if !ok { - return nil, dataMismatchError(encType, encValue) - } - - arrayBuffer := bytes.Buffer{} - parsedType := strings.Split(encType, "[")[0] - for _, item := range arrayValue { - if typedData.Types[parsedType] != nil { - mapValue, ok := item.(map[string]interface{}) - if !ok { - return nil, dataMismatchError(parsedType, item) - } - encodedData, err := typedData.EncodeData(parsedType, mapValue, depth+1) - if err != nil { - return nil, err - } - arrayBuffer.Write(encodedData) - } else { - encValue, err := typedData.EncodePrimitiveValue(encType, encValue, depth) - if err != nil { - return nil, err - } - bytesValue, err := bytesValueOf(encValue) - if err != nil { - return nil, err - } - arrayBuffer.Write(bytesValue) - } - } - - buffer.Write(crypto.Keccak256(arrayBuffer.Bytes())) - } else if typedData.Types[field["type"]] != nil { - mapValue, ok := encValue.(map[string]interface{}) - if !ok { - return nil, dataMismatchError(encType, encValue) - } +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, + }) - encodedData, err := typedData.EncodeData(field["type"], mapValue, depth+1) - if err != nil { - return nil, err - } + var buffer bytes.Buffer - buffer.Write(crypto.Keccak256(encodedData)) - } else { - primitiveEncValue, err := typedData.EncodePrimitiveValue(encType, encValue, depth) - if err != nil { - return nil, err - } - bytesValue, err := bytesValueOf(primitiveEncValue) - if err != nil { - return nil, err + 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) } - buffer.Write(bytesValue) } - } - - return buffer.Bytes(), nil -} - -// EncodePrimitiveValue deals with the primitive values found -// while searching through the typed data -func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interface{}, depth int) (interface{}, error) { - var primitiveEncValue interface{} + break - switch encType { - case "address": - bytesValue := hexutil.Bytes{} - for i := 0; i < 12; i++ { - bytesValue = append(bytesValue, 0) - } - stringValue, ok := encValue.(string) - if !ok || !common.IsHexAddress(stringValue) { - return nil, dataMismatchError(encType, encValue) - } - addressValue := common.HexToAddress(stringValue) - for _, _byte := range addressValue { - bytesValue = append(bytesValue, _byte) - } - primitiveEncValue = bytesValue - case "bool": + case bool: + boolVal, _ := val.(bool) var int64Val int64 - boolValue, ok := encValue.(bool) - if !ok { - return nil, dataMismatchError(encType, encValue) - } - if boolValue { + if boolVal { int64Val = 1 } - primitiveEncValue = abi.U256(big.NewInt(int64Val)) - case "bytes", "string": - bytesValue, err := bytesValueOf(encValue) - if err != nil { - return nil, dataMismatchError(encType, encValue) - } - primitiveEncValue = crypto.Keccak256(bytesValue) - default: - if strings.HasPrefix(encType, "bytes") { - sizeStr := strings.TrimPrefix(encType, "bytes") - size, _ := strconv.Atoi(sizeStr) - bytesValue := hexutil.Bytes{} - for i := 0; i < 32-size; i++ { - bytesValue = append(bytesValue, 0) - } - if _, ok := encValue.(hexutil.Bytes); !ok { - return nil, dataMismatchError(encType, encValue) - } - bytesValue = append(bytesValue, encValue.(hexutil.Bytes)...) - primitiveEncValue = bytesValue - } else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { - bigIntValue, ok := encValue.(*big.Int) - if !ok { - return nil, dataMismatchError(encType, encValue) - } - primitiveEncValue = abi.U256(bigIntValue) - } else { - return nil, fmt.Errorf("unrecognized type '%s'", encType) - } - } - return primitiveEncValue, nil -} - -// dataMismatchError generates an error for a mismatch between -// the provided type and data -func dataMismatchError(encType string, encValue interface{}) error { - return fmt.Errorf("provided data '%v' doesn't match type '%s'", encValue, encType) -} + encodedVal := abi.U256(big.NewInt(int64Val)) + fmt.Printf("bool encoded value:", encodedVal) + buffer.Write(encodedVal) + break -// bytesValuesOf returns the bytes value of the given interface -func bytesValueOf(_interface interface{}) (hexutil.Bytes, error) { - bytesValue, ok := _interface.(hexutil.Bytes) - if ok { - return bytesValue, nil - } + case string: + bytesVal := common.FromHex(val.(string)) + hash := common.BytesToHash(crypto.Keccak256(bytesVal)) + buffer.WriteString(hash.String()) + break - switch reflect.TypeOf(_interface) { - case reflect.TypeOf(hexutil.Bytes{}): - return _interface.(hexutil.Bytes), nil - case reflect.TypeOf([]byte{}): - return hexutil.Bytes(_interface.([]byte)), nil - case reflect.TypeOf([]uint8{}): - return _interface.([]uint8), nil - case reflect.TypeOf(string("")): - return hexutil.Bytes(_interface.(string)), nil default: + arr := [...]string{"(a)", "(b)", "(c)"} + rand.Seed(time.Now().UnixNano()) + buffer.WriteString(arr[rand.Intn(3)]) break } - return nil, fmt.Errorf("unrecognized type '%T'", _interface) -} - -// EcRecover recovers the address associated with the given sig. -// Only compatible with `text/plain` -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. - // - // Note, this function is compatible with eth_sign and personal_sign. As such it recovers - // the address of: - // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") - // addr = ecrecover(hash, signature) - // - // Note, the signature must conform to the secp256k1 curve R, S and V values, where - // the V value must be be 27 or 28 for legacy reasons. - // - // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover - if len(sig) != 65 { - return common.Address{}, fmt.Errorf("signature must be 65 bytes long") - } - if sig[64] != 27 && sig[64] != 28 { - return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") - } - sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 - hash, _ := SignTextPlain(data) - rpk, err := crypto.SigToPub(hash, sig) - if err != nil { - return common.Address{}, err - } - return crypto.PubkeyToAddress(*rpk), nil -} - -// UnmarshalValidatorData converts the bytes input to typed data -func UnmarshalValidatorData(data interface{}) (ValidatorData, error) { - raw := data.(map[string]interface{}) - - addr, ok := raw["address"].(string) - if !ok { - return ValidatorData{}, errors.New("validator address is not sent as a string") - } - addrBytes, err := hexutil.Decode(addr) - if err != nil { - return ValidatorData{}, err - } - if !ok || len(addrBytes) == 0 { - return ValidatorData{}, errors.New("validator address is undefined") - } - - message, ok := raw["message"].(string) - if !ok { - return ValidatorData{}, errors.New("message is not sent as a string") - } - messageBytes, err := hexutil.Decode(message) - if err != nil { - return ValidatorData{}, err - } - if !ok || len(messageBytes) == 0 { - return ValidatorData{}, errors.New("message is undefined") - } - - return ValidatorData{ - Address: common.BytesToAddress(addrBytes), - Message: messageBytes, - }, nil -} - -// Validate make sure the types are sound -func (typedData *TypedData) Validate() error { - if err := typedData.Types.Validate(); err != nil { - return err - } - if err := typedData.Domain.Validate(); err != nil { - return err - } - return nil -} - -// Map generates a map version of the typed data -func (typedData *TypedData) Map() map[string]interface{} { - dataMap := map[string]interface{}{ - "types": typedData.Types, - "domain": typedData.Domain.Map(), - "primaryType": typedData.PrimaryType, - "message": typedData.Message, - } - return dataMap -} - -// PrettyPrint generates a nice output to help the users -// of clef present data in their apps -func (typedData *TypedData) PrettyPrint() string { - output := bytes.Buffer{} - - output.WriteString(fmt.Sprintf("%s {\n", "Domain")) - output.WriteString(typedData.PrettyPrintData("EIP712Domain", typedData.Domain.Map(), 1)) - output.Truncate(output.Len() - 2) - output.WriteString(fmt.Sprintf("\n}\n")) - - output.WriteString(fmt.Sprintf("%s {\n", typedData.PrimaryType)) - output.WriteString(typedData.PrettyPrintData(typedData.PrimaryType, typedData.Message, 1)) - output.Truncate(output.Len() - 2) - output.WriteString(fmt.Sprintf("\n}")) - - return output.String() + return buffer.String() } -// PrettyPrintData generates a formatted output for the -// given data -func (typedData *TypedData) PrettyPrintData(primaryType string, data map[string]interface{}, depth int) string { - output := bytes.Buffer{} - - // Add field contents. Structs and arrays have special handlers. - for _, field := range typedData.Types[primaryType] { - encType := field["type"] - encName := field["name"] - encValue := data[encName] - - if encType[len(encType)-1:] == "]" { - arrayValue, _ := encValue.([]interface{}) - parsedType := strings.Split(encType, "[")[0] - for _, item := range arrayValue { - if typedData.Types[parsedType] != nil { - mapValue, _ := item.(map[string]interface{}) - mapOutput := typedData.PrettyPrintData(parsedType, mapValue, depth+1) - output.WriteString(mapOutput) - } else { - primitiveOutput := typedData.PrettyPrintPrimitiveValue(encType, encName, encValue, depth) - output.WriteString(primitiveOutput) - } - } - } else if typedData.Types[field["type"]] != nil { - output.WriteString(strings.Repeat("\u00a0", depth*2)) - output.WriteString(fmt.Sprintf("\"%s\": { %s\n", field["name"], encType)) - - mapValue, _ := encValue.(map[string]interface{}) - mapOutput := typedData.PrettyPrintData(field["type"], mapValue, depth+1) - output.WriteString(mapOutput) - - output.Truncate(output.Len() - 2) - output.WriteString(fmt.Sprintf("\n%s},\n", strings.Repeat("\u00a0", depth*2))) - } else { - primitiveOutput := typedData.PrettyPrintPrimitiveValue(encType, encName, encValue, depth) - output.WriteString(primitiveOutput) +// isStandardType checks if the given type is a EIP712 conformant type +func isStandardType(typeStr string) bool { + standardTypes := []string{ + "array", + "address", + "boolean", + "bytes", + "string", + "struct", + "uint", + } + for _, val := range standardTypes { + if strings.HasPrefix(typeStr, val) { + return true } } - - return output.String() + return false } -// PrettyPrintPrimitiveValue generates a formatted output for the -// given primitive value -func (typedData *TypedData) PrettyPrintPrimitiveValue(encType string, encName string, encValue interface{}, depth int) string { - output := bytes.Buffer{} - output.WriteString(strings.Repeat("\u00a0", depth*2)) - output.WriteString(fmt.Sprintf("\"%s\": ", encName)) - - switch encType { - case "address": - stringValue, _ := encValue.(string) - addressValue := common.HexToAddress(stringValue) - output.WriteString(fmt.Sprintf("%s,\n", addressValue.String())) - case "bool": - boolValue, _ := encValue.(bool) - output.WriteString(fmt.Sprintf("%t,\n", boolValue)) - case "bytes", "string": - output.WriteString(fmt.Sprintf("\"%s\",\n", encValue)) - default: - if strings.HasPrefix(encType, "bytes") { - output.WriteString(fmt.Sprintf("\"%s\",\n", encValue)) - } else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { - bigIntValue, _ := encValue.(*big.Int) - output.WriteString(fmt.Sprintf("%d,\n", bigIntValue)) - } +// 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}) } - return output.String() -} + // 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 + }) -// Validate checks if the types object is conformant to the specs -func (types *Types) Validate() error { - for typeKey, typeArr := range *types { - for _, typeObj := range typeArr { - typeVal := typeObj["type"] - if typeKey == typeVal { - return fmt.Errorf("type '%s' cannot reference itself", typeVal) - } - firstChar := []rune(typeVal)[0] - if unicode.IsUpper(firstChar) { - if (*types)[typeVal] == nil { - return fmt.Errorf("referenced type '%s' is undefined", typeVal) - } - } else { - if !typedDataRegexp.MatchString(typeVal) { - if (*types)[typeVal] != nil { - return fmt.Errorf("referenced type '%s' must be capitalized", typeVal) - } else { - return fmt.Errorf("unknown atomic type '%s'", typeVal) - } - } - } - } + for _, priority := range priorities { + fmt.Printf("%s, Value %d\n", priority.Type, priority.Value) } - return nil + fmt.Printf("\n") + + return priorities } -// Validate checks if the given domain is valid, i.e. contains at least +// IsValid checks if the given domain is valid, i.e. contains at least // the minimum viable keys and values -func (domain *TypedDataDomain) Validate() error { +func (domain *EIP712Domain) IsValid() error { if domain.ChainId == big.NewInt(0) { return errors.New("chainId must be specified according to EIP-155") } if len(domain.Name) == 0 && len(domain.Version) == 0 && len(domain.VerifyingContract) == 0 && len(domain.Salt) == 0 { - return errors.New("domain is undefined") + return errors.New("domain undefined") } return nil } -// Map is a helper function to generate a map version of the domain -func (domain *TypedDataDomain) Map() map[string]interface{} { - dataMap := map[string]interface{}{ - "chainId": domain.ChainId, - } - - if len(domain.Name) > 0 { - dataMap["name"] = domain.Name - } - - if len(domain.Version) > 0 { - dataMap["version"] = domain.Version - } - - if len(domain.VerifyingContract) > 0 { - dataMap["verifyingContract"] = domain.VerifyingContract - } - - if len(domain.Salt) > 0 { - dataMap["salt"] = domain.Salt +// 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, + "version": domain.Version, + "chainId": domain.Name, + "verifyingContract": domain.VerifyingContract, + "salt": domain.Salt, } - return dataMap } From fb23ff9d73bec60e9a670b4a9ecc44bfe7a1cc67 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sun, 21 Oct 2018 13:58:00 +0100 Subject: [PATCH 46/84] Drafted recursive encodeData --- cmd/clef/audit.log | 84 ++++++ signer/core/api.go | 178 ------------ signer/core/api_layout.md | 24 +- signer/core/signed_data.go | 571 +++++++++++++++++++++++++++++-------- 4 files changed, 562 insertions(+), 295 deletions(-) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log index 9b0ef72b850f..667d0443b4f6 100644 --- a/cmd/clef/audit.log +++ b/cmd/clef/audit.log @@ -62,3 +62,87 @@ t=2018-10-15T03:12:04+0100 lvl=info msg=SignTypedData api=signer type=response d t=2018-10-15T03:14:48+0100 lvl=info msg=Configured api=signer audit log=audit.log t=2018-10-15T03:15:25+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50635\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" t=2018-10-15T03:15:25+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-20T15:17:32+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:17:53+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:22:24+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:24:48+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:24:54+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56168\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[] PrimaryType: Domain:{Name: Version: ChainId: VerifyingContract:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Salt:0x} Message:map[]}" +t=2018-10-20T15:24:54+0100 lvl=info msg=SignTypedData api=signer type=response data= error="primary type undefined" +t=2018-10-20T15:25:28+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:27:12+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:27:59+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:28:02+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56262\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[] PrimaryType: Domain:{Name: Version: ChainId: VerifyingContract:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Salt:0x} Message:map[]}" +t=2018-10-20T15:28:02+0100 lvl=info msg=SignTypedData api=signer type=response data= error="primary type undefined" +t=2018-10-20T15:28:15+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56262\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[] PrimaryType: Domain:{Name: Version: ChainId: VerifyingContract:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Salt:0x} Message:map[]}" +t=2018-10-20T15:28:15+0100 lvl=info msg=SignTypedData api=signer type=response data= error="primary type undefined" +t=2018-10-20T15:29:47+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:29:52+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56281\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[] PrimaryType: Domain:{Name: Version: ChainId: VerifyingContract:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Salt:0x} Message:map[]}" +t=2018-10-20T15:32:20+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:32:46+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56456\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:Person name:from] map[type:Person name:to] 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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-20T15:34:35+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:35:24+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56476\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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:[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-20T15:37:05+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:37:21+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56561\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:Person name:to] 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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T15:38:27+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:38:36+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56583\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[type:string name:version] 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[to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" +t=2018-10-20T15:40:49+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:40:54+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56604\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T15:41:20+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:41:38+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56681\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[type:string name:name] map[type:string name:version] 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:[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-20T15:49:10+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:49:20+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56804\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:string name:contents]]] 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-20T15:51:01+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:51:31+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56859\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Mail:[map[type:Person name:from] 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]] Person:[map[name:name type:string] map[name:wallet 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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-20T15:51:59+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:52:23+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56905\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T16:05:44+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:05:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58137\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-20T16:06:22+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:06:25+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58154\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[type:string name:name] map[type:string name:version] 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[type:Person name:to] 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-20T16:06:39+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:06:43+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58213\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[type:string name:name] map[name:version type:string] map[name:chainId type:uint256] map[type:address name:verifyingContract]] 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:[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-20T16:06:55+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:07:53+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58301\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] Person:[map[type:string name:name] map[name:wallet 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[contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]]}" +t=2018-10-20T16:10:03+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:10:05+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58446\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:string name:contents]] 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:[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-20T16:10:39+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:10:43+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58497\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T16:12:31+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:12:37+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58538\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[type:string name:version] 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:[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-20T16:12:55+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58538\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T16:14:08+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:14:11+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58563\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[type:string name:version] map[name:chainId type:uint256] map[type:address name:verifyingContract]] 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:[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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice]]}" +t=2018-10-20T16:17:07+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:17:13+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58619\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T16:18:51+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:19:01+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:20:03+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58718\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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:[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-20T16:24:34+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:25:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58919\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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-20T16:30:18+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:30:40+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58983\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T16:30:59+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:31:01+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59036\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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-20T16:31:36+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:32:25+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59121\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice]]}" +t=2018-10-20T19:01:23+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:01:51+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59614\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] Person:[map[name:name type:string] map[name:wallet 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-20T19:02:26+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:02:41+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59722\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T19:06:00+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:07:47+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60455\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T19:08:16+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60455\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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[contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]]}" +t=2018-10-20T19:09:46+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60587\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T19:11:12+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:11:21+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60710\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" +t=2018-10-20T19:11:35+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60710\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] 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-20T19:11:51+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:11:53+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60768\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] 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-20T19:12:11+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:12:30+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60809\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" +t=2018-10-20T19:13:53+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:14:04+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60898\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T19:14:32+0100 lvl=info msg=SignTypedData api=signer type=response data=686365d6fa3d2a98d7c67101a8acf295c644a8de80b8ecc89cff276355897f6d error=nil +t=2018-10-20T19:36:35+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:36:40+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61953\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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-20T19:36:40+0100 lvl=info msg=SignTypedData api=signer type=response data=7951623617a41fec181fafc3555c0a494d01dce0408b6596964f5f50acba239e error=nil diff --git a/signer/core/api.go b/signer/core/api.go index 11e52144e29d..141c1df9d858 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -21,11 +21,8 @@ import ( "encoding/json" "errors" "fmt" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto/sha3" "io/ioutil" "math/big" - "mime" "reflect" "github.com/ethereum/go-ethereum/accounts" @@ -545,181 +542,6 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth } -// SignData signs the hash of the provided data, but does so differently -// depending on the content-type specified. -// -// Depending on the content-type, different types of validations will occur. -// -// Note, the produced signature conforms to the secp256k1 curve R, S and V values, -// where the V value will be 27 or 28 for legacy reasons. -func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { - - var req, err = api.determineSignatureFormat(contentType, data) - if err != nil { - return nil, err - } - req.Address = addr - req.Meta = MetadataFromContext(ctx) - - // We make the request prior to looking up if we actually have the account, to prevent - // account-enumeration via the API - res, err := api.UI.ApproveSignData(req) - if err != nil { - return nil, err - } - if !res.Approved { - return nil, ErrRequestDenied - } - // Look up the wallet containing the requested signer - account := accounts.Account{Address: addr.Address()} - wallet, err := api.am.Find(account) - if err != nil { - return nil, err - } - // Sign the data with the wallet - signature, err := wallet.SignHashWithPassphrase(account, res.Password, req.Hash) - if err != nil { - api.UI.ShowError(err.Error()) - return nil, err - } - signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper - return signature, nil -} - -// Determines which signature method should be used based upon the mime type -func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil.Bytes) (*SignDataRequest, error) { - var req *SignDataRequest - mediaType, _, err := mime.ParseMediaType(contentType) - if err != nil { - return nil, err - } - switch mediaType { - case TextValidator.Mime: - // Data with an intended validator - - sighash, msg := signTextWithValidator(data) - req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - case TextPlain.Mime: - // Sign calculates an Ethereum ECDSA signature for: - // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") - - // In the cases where it matters ensure that the charset is handled. The charset - // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType - // charset, ok := params["charset"] - // As it is now, we accept any charset and just treat it as 'raw'. - - sighash, msg := signTextPlain(data) - req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - case ApplicationClique.Mime: - // Clique is the Ethereum PoA standard - header := &types.Header{} - if err := rlp.DecodeBytes(data, header); err != nil { - return nil, err - } - sighash, err := signCliqueHeader(header) - if err != nil { - return nil, err - } - msg := fmt.Sprintf("Clique block %d [0x%x]", header.Number, header.Hash()) - req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - default: - return nil, fmt.Errorf("content type '%s' not implemented for signing", contentType) - } - return req, nil - -} - -// signTextPlain is a helper function that calculates a hash for the given message that can be -// safely used to calculate a signature from. -// -// The hash is calculated as -// keccak256("\x19${byteVersion}Ethereum Signed Message:\n"${message length}${message}). -// -// This gives context to the signed message and prevents signing of transactions. -func signTextPlain(data []byte) ([]byte, string) { - msg := fmt.Sprintf("\x19\\x%xEthereum Signed Message:\n%d%s", TextPlain.ByteVersion, len(data), data) - return crypto.Keccak256([]byte(msg)), msg -} - -// signTextWithValidator signs the given message which can be further recovered -// with the given validator. -func signTextWithValidator(data []byte) ([]byte, string) { - msg := "TODO" - return crypto.Keccak256([]byte(msg)), msg -} - -// signCliqueHeader returns the hash which is used as input for the proof-of-authority -// signing. It is the hash of the entire header apart from the 65 byte signature -// contained at the end of the extra data. -// -// The method requires the extra data to be at least 65 bytes -- the original implementation -// in clique.go panics if this is the case, thus it's been reimplemented here to avoid the panic -// and simply return an error instead -func signCliqueHeader(header *types.Header) (hexutil.Bytes, error) { - hash := common.Hash{} - if len(header.Extra) < 65 { - return hash.Bytes(), fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) - } - hasher := sha3.NewKeccak256() - rlp.Encode(hasher, []interface{}{ - header.ParentHash, - header.UncleHash, - header.Coinbase, - header.Root, - header.TxHash, - header.ReceiptHash, - header.Bloom, - header.Difficulty, - header.Number, - header.GasLimit, - header.GasUsed, - header.Time, - header.Extra[:len(header.Extra)-65], - header.MixDigest, - header.Nonce, - }) - hasher.Sum(hash[:0]) - return hash.Bytes(), nil -} - -// Determines the content type and then recovers the address associated with the given sig -func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { - mediaType, _, err := mime.ParseMediaType(contentType) - if err != nil { - return common.Address{}, err - } - switch mediaType { - case TextPlain.Mime: - // 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 - // the address of: - // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") - // addr = ecrecover(hash, signature) - // - // Note, the signature must conform to the secp256k1 curve R, S and V values, where - // the V value must be be 27 or 28 for legacy reasons. - // - // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover - - if len(sig) != 65 { - return common.Address{}, fmt.Errorf("signature must be 65 bytes long") - } - if sig[64] != 27 && sig[64] != 28 { - return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") - } - sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 - hash, _ := signTextPlain(data) - rpk, err := crypto.SigToPub(hash, sig) - if err != nil { - return common.Address{}, err - } - return crypto.PubkeyToAddress(*rpk), nil - default: - return common.Address{}, fmt.Errorf("content type '%s' not implemented for ecRecover", contentType) - } -} - // Export returns encrypted private key associated with the given address in web3 keystore format. func (api *SignerAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) { res, err := api.UI.ApproveExport(&ExportRequest{Address: addr, Meta: MetadataFromContext(ctx)}) diff --git a/signer/core/api_layout.md b/signer/core/api_layout.md index 1a251fe9eef6..3aae11fd07b7 100644 --- a/signer/core/api_layout.md +++ b/signer/core/api_layout.md @@ -1,7 +1,7 @@ # Specs -`encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01" β€– domainSeparator β€– hashStruct(message)` +`encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01EthereumSignedMessage\n" β€– domainSeparator β€– hashStruct(message)` - data adheres to π•Š, a structure defined in the rigorous eip-712 -- `\x00` is needed to comply with EIP-191 +- `\x01` is needed to comply with EIP-191 - `domainSeparator` and `hashStruct` are defined below ## A) domainSeparator @@ -34,9 +34,9 @@ Example: `Mail(Person from,Person to,string contents)Person(string name,address #### a) atomic - - `boolean` => `uint256` + - `bool` => `uint256` - `address` => `uint160` - - `uint` => sign-extended `uint256` in big endian order + - `int8:int256` and `uint8:uint256` => sign-extended `uint256` in big endian order - `bytes1:31` => `bytes32` #### b) dynamic @@ -49,7 +49,21 @@ Example: `Mail(Person from,Person to,string contents)Person(string name,address - `array` => `keccak256(encodeData(array))` - `struct` => `rec(keccak256(hashStruct(struct)))` -## C) Example +## C) Algo +- hashStruct + - encodeType + - encodeData + - if primitive + - encode + - else + - if array + - encodeData + - else if struct + - hashStruct + - else + - break + +## D) Example ### Query ```json { diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 79e49989ac42..eef2c89b3c96 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -4,14 +4,18 @@ import ( "bytes" "context" "encoding/hex" + "encoding/json" "errors" "fmt" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto/sha3" + "github.com/ethereum/go-ethereum/rlp" "math/big" - "math/rand" + "mime" "reflect" "sort" "strings" - "time" "unicode" "github.com/PaulRBerg/basics/helpers" @@ -22,14 +26,16 @@ import ( ) type TypedData struct { - Types map[string]EIP712Type `json:"types"` - PrimaryType string `json:"primaryType"` - Domain EIP712Domain `json:"domain"` - Message EIP712Message `json:"message"` + Types EIP712Types `json:"types"` + PrimaryType string `json:"primaryType"` + Domain EIP712Domain `json:"domain"` + Message EIP712Data `json:"message"` } type EIP712Type []map[string]string +type EIP712Types map[string]EIP712Type + type EIP712TypePriority struct { Type string Value uint @@ -38,79 +44,255 @@ 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 common.Address `json:"verifyingContract"` + Salt hexutil.Bytes `json:"salt"` } -type EIP712Message map[string]interface{} +const ( + TypeArray = "array" + TypeAddress = "address" + TypeBool = "bool" + TypeBytes = "bytes" + TypeInt = "int" + TypeString = "string" +) -// 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 { +// Sign receives a request and produces a signature + +// Note, the produced signature conforms to the secp256k1 curve R, S and V values, +// where the V value will be 27 or 28 for legacy reasons. +func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, req *SignDataRequest) ([]byte, error) { + req.Address = addr + req.Meta = MetadataFromContext(ctx) + + // We make the request prior to looking up if we actually have the account, to prevent + // account-enumeration via the API + res, err := api.UI.ApproveSignData(req) + if err != nil { + return nil, err + } + if !res.Approved { + return nil, ErrRequestDenied + } + // Look up the wallet containing the requested signer + account := accounts.Account{Address: addr.Address()} + wallet, err := api.am.Find(account) + if err != nil { + return nil, err + } + // Sign the data with the wallet + signature, err := wallet.SignHashWithPassphrase(account, res.Password, req.Hash) + if err != nil { + return nil, err + } + signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper + return signature, nil +} + +// SignData signs the hash of the provided data, but does so differently +// depending on the content-type specified. +// +// Different types of validation occur. +func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { + var req, err = api.determineSignatureFormat(contentType, data) + if err != nil { + return nil, err + } + + signature, err := api.Sign(ctx, addr, req) + if err != nil { + api.UI.ShowError(err.Error()) + return nil, err + } + + return signature, nil +} + +// Determines which signature method should be used based upon the mime type +func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil.Bytes) (*SignDataRequest, error) { + var req *SignDataRequest + mediaType, _, err := mime.ParseMediaType(contentType) + if err != nil { return nil, err } - if data.PrimaryType == "" { - return nil, errors.New("primary type undefined") + + switch mediaType { + case TextValidator.Mime: + // Data with an intended validator + sighash, msg := signTextWithValidator(data) + req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + break + case TextPlain.Mime: + // Sign calculates an Ethereum ECDSA signature for: + // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") + + // In the cases where it matters ensure that the charset is handled. The charset + // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType + // charset, ok := params["charset"] + // As it is now, we accept any charset and just treat it as 'raw'. + sighash, msg := signTextPlain(data) + req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + break + case ApplicationClique.Mime: + // Clique is the Ethereum PoA standard + header := &types.Header{} + if err := rlp.DecodeBytes(data, header); err != nil { + return nil, err + } + sighash, err := signCliqueHeader(header) + if err != nil { + return nil, err + } + msg := fmt.Sprintf("Clique block %d [0x%x]", header.Number, header.Hash()) + req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + break + default: + return nil, fmt.Errorf("content type '%s' not implemented for signing", contentType) } + return req, nil + +} - domainTypes := map[string]EIP712Type{ - "EIP712Domain": data.Types["EIP712Domain"], +// signTextPlain is a helper function that calculates a hash for the given message that can be +// safely used to calculate a signature from. +// +// The hash is calculated as +// keccak256("\x19${byteVersion}Ethereum Signed Message:\n"${message length}${message}). +// +// This gives context to the signed message and prevents signing of transactions. +func signTextPlain(data []byte) ([]byte, string) { + msg := fmt.Sprintf("\x19\\x%xEthereum Signed Message:\n%d%s", TextPlain.ByteVersion, len(data), data) + return crypto.Keccak256([]byte(msg)), msg +} + +// signTextWithValidator signs the given message which can be further recovered +// with the given validator. +func signTextWithValidator(data []byte) ([]byte, string) { + msg := "TODO" + return crypto.Keccak256([]byte(msg)), msg +} + +// signCliqueHeader returns the hash which is used as input for the proof-of-authority +// signing. It is the hash of the entire header apart from the 65 byte signature +// contained at the end of the extra data. +// +// The method requires the extra data to be at least 65 bytes -- the original implementation +// in clique.go panics if this is the case, thus it's been reimplemented here to avoid the panic +// and simply return an error instead +func signCliqueHeader(header *types.Header) (hexutil.Bytes, error) { + hash := common.Hash{} + if len(header.Extra) < 65 { + return hash.Bytes(), fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) } - domainSeparator := hashStruct(domainTypes, data.PrimaryType, data.Domain.Values(), 0) + hasher := sha3.NewKeccak256() + rlp.Encode(hasher, []interface{}{ + header.ParentHash, + header.UncleHash, + header.Coinbase, + header.Root, + header.TxHash, + header.ReceiptHash, + header.Bloom, + header.Difficulty, + header.Number, + header.GasLimit, + header.GasUsed, + header.Time, + header.Extra[:len(header.Extra)-65], + header.MixDigest, + header.Nonce, + }) + hasher.Sum(hash[:0]) + return hash.Bytes(), nil +} + +// SignTypedData signs EIP712 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 := hashStruct(domainTypes, typedData.Domain.Map(), "EIP712Domain", 0) + domainSeparator := common.Bytes2Hex(domainSeparatorBytes) //if err != nil { // return nil, err //} - delete(data.Types, "EIP712Domain") - typedDataHash := hashStruct(data.Types, data.PrimaryType, data.Message, 0) + delete(typedData.Types, "EIP712Domain") + typedDataHashBytes := hashStruct(typedData.Types, typedData.Message, typedData.PrimaryType, 0) + typedDataHash := common.Bytes2Hex(typedDataHashBytes) //if err != nil { // return nil, err //} - fmt.Println("domainSeparator", domainSeparator.String()) - fmt.Println("typedDataHash", typedDataHash.String()) - return common.FromHex("0xdeadbeef"), nil + fmt.Println("domainSeparator", domainSeparator) + fmt.Println("typedDataHash", typedDataHash) + + var buffer bytes.Buffer + buffer.WriteString("\x19\x01") + buffer.WriteString(domainSeparator) + buffer.WriteString(typedDataHash) + + sighash := crypto.Keccak256(buffer.Bytes()) + msg := fmt.Sprintf("\x19\\x%xEthereum Signed Message\n:%s%s", DataTyped.ByteVersion, domainSeparator, typedDataHash) + //req := &SignDataRequest{Rawdata: typedData, Message: msg, Hash: sighash, ContentType: DataTyped.Mime} + + var req, err = api.determineSignatureFormat(contentType, data) + if err != nil { + return nil, err + } + + signature, err := api.Sign(ctx, addr, req) + if err != nil { + api.UI.ShowError(err.Error()) + return nil, err + } + + return signature, nil + + return crypto.Keccak256(buffer.Bytes()), nil } // hashStruct generates the following encoding for the given domain and message: // `encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01" β€– domainSeparator β€– hashStruct(message)` -func hashStruct(types map[string]EIP712Type, key string, data EIP712Data, depth int) common.Hash { +func hashStruct(_types EIP712Types, data EIP712Data, dataType string, depth int) []byte { helpers.PrintJson("hashStruct", map[string]interface{}{ "depth": depth, }) - typeEncoding := encodeType(types) - typeHash := hex.EncodeToString(crypto.Keccak256([]byte(typeEncoding))) + typeEncoding := encodeType(_types) + typeHash := hex.EncodeToString(crypto.Keccak256(typeEncoding)) - dataEncoding := encodeData(types, key, data, depth) - dataHash := hex.EncodeToString(crypto.Keccak256([]byte(dataEncoding))) + dataEncoding := encodeData(_types, data, dataType, depth) + dataHash := hex.EncodeToString(crypto.Keccak256(dataEncoding)) var buffer bytes.Buffer buffer.WriteString(typeHash) buffer.WriteString(dataHash) - hash := common.BytesToHash(crypto.Keccak256(buffer.Bytes())) + encoding := crypto.Keccak256(buffer.Bytes()) if depth == 0 { - fmt.Printf("typeEncoding %s\n", typeEncoding) - fmt.Printf("dataEncoding %s\n", dataEncoding) + fmt.Printf("typeEncoding %s\n", common.Bytes2Hex(typeEncoding)) + fmt.Printf("dataEncoding %s\n", common.Bytes2Hex(dataEncoding)) } - return hash + + 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 encodeType(types map[string]EIP712Type) string { - helpers.PrintJson("hashStruct", map[string]interface{}{ - "types": types, +func encodeType(_types EIP712Types) []byte { + helpers.PrintJson("encodeType", map[string]interface{}{ + "types": _types, }) + //fmt.Printf("encodeType: types %v\n\n", types) var priorities = make(map[string]uint) - for key := range types { + for key := range _types { priorities[key] = 0 } @@ -119,7 +301,7 @@ func encodeType(types map[string]EIP712Type) string { priorities[typeVal]++ // Importantly, we also have to check for parent types to increment them too - for _, typeObj := range types[typeVal] { + for _, typeObj := range _types[typeVal] { _typeVal := typeObj["type"] firstChar := []rune(_typeVal)[0] @@ -139,32 +321,22 @@ func encodeType(types map[string]EIP712Type) string { return false } - for typeKey, typeArr := range types { + for typeKey, typeArr := range _types { var typeValArr []string for _, typeObj := range typeArr { typeVal := typeObj["type"] - if typeKey == typeVal { - panic(fmt.Errorf("type %s cannot reference itself", typeVal)) - } + //if typeKey == typeVal { + // panic(fmt.Errorf("type %s cannot reference itself", typeVal)) + //} firstChar := []rune(typeVal)[0] if unicode.IsUpper(firstChar) { - if types[typeVal] != nil { + if _types[typeVal] != nil { if !visited(typeValArr, typeVal) { typeValArr = append(typeValArr, typeVal) update(typeKey, typeVal) } - } else { - panic(fmt.Errorf("referenced type %s is undefined", typeVal)) - } - } else { - if !isStandardType(typeVal) { - if types[typeVal] != nil { - panic(fmt.Errorf("Custom type %s must be capitalized", typeVal)) - } else { - panic(fmt.Errorf("Unknown type %s", typeVal)) - } } } } @@ -176,7 +348,7 @@ func encodeType(types map[string]EIP712Type) string { var buffer bytes.Buffer for _, priority := range sortedPriorities { typeKey := priority.Type - typeArr := types[typeKey] + typeArr := _types[typeKey] buffer.WriteString(typeKey) buffer.WriteString("(") @@ -192,64 +364,183 @@ func encodeType(types map[string]EIP712Type) string { buffer.WriteString(")") } - return buffer.String() + return buffer.Bytes() +} + +// 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 + }) + + //for _, priority := range priorities { + // fmt.Printf("%s, Value %d\n", priority.Type, priority.Value) + //} + //fmt.Printf("\n") + + return priorities } // 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, +func encodeData(_types EIP712Types, data interface{}, dataType string, depth int) []byte { + helpers.PrintJson("encodeData", map[string]interface{}{ + "dataType": dataType, + "data": data, "depth": depth, }) var buffer bytes.Buffer - switch val.(type) { - case EIP712Data: - for mapKey, mapVal := range val.(EIP712Data) { + // TODO regex + // handle arrays + if strings.Contains(dataType, "[]") { + arrayVal := data.([]interface{}) + dataType := "TODO" + + var arrayBuffer bytes.Buffer + for obj := range arrayVal { + objEncoding := encodeData(_types, obj, dataType, depth+1) + arrayBuffer.Write(objEncoding) + } + + 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{}) { - hash := hashStruct(types, mapKey, mapVal.(EIP712Data), depth+1) - buffer.WriteString(hash.String()) + data := mapVal.(map[string]interface{}) + encoding := hashStruct(_types, data, nextDataType, depth+1) + buffer.Write(encoding) } else { - str := encodeData(types, mapKey, mapVal, depth+1) - buffer.WriteString(str) + encoding := encodeData(_types, mapVal, nextDataType, depth+1) + buffer.Write(encoding) } } - break + 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() + } - case bool: - boolVal, _ := val.(bool) + // 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 } - encodedVal := abi.U256(big.NewInt(int64Val)) - fmt.Printf("bool encoded value:", encodedVal) - buffer.Write(encodedVal) + encoding := abi.U256(big.NewInt(int64Val)) + buffer.Write(encoding) break - - case string: - bytesVal := common.FromHex(val.(string)) - hash := common.BytesToHash(crypto.Keccak256(bytesVal)) - buffer.WriteString(hash.String()) + case TypeString: + bytesVal := common.FromHex(data.(string)) + encoding := crypto.Keccak256(bytesVal) + buffer.Write(encoding) break - default: - arr := [...]string{"(a)", "(b)", "(c)"} - rand.Seed(time.Now().UnixNano()) - buffer.WriteString(arr[rand.Intn(3)]) break } - return buffer.String() + return buffer.Bytes() +} + +// 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 { + Types EIP712Types `json:"types"` + PrimaryType string `json:"primaryType"` + Domain EIP712Domain `json:"domain"` + Message EIP712Data `json:"message"` + } + + var raw input + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + if raw.Types == nil { + return errors.New("types are undefined") + } + if err := raw.Types.IsValid(); err != nil { + return err + } + typedData.Types = raw.Types + + if raw.Types["EIP712Domain"] == nil { + return errors.New("domain types are undefined") + } + if err := raw.Domain.IsValid(); err != nil { + return err + } + typedData.Domain = raw.Domain + + if len(raw.PrimaryType) == 0 { + return errors.New("primary type is undefined") + } + typedData.PrimaryType = raw.PrimaryType + + if raw.Message == nil { + return errors.New("message is undefined") + } + typedData.Message = raw.Message + + return nil } // isStandardType checks if the given type is a EIP712 conformant type -func isStandardType(typeStr string) bool { +func isStandardTypeStr(typeStr string) bool { standardTypes := []string{ "array", "address", @@ -267,28 +558,33 @@ func isStandardType(typeStr string) bool { return false } -// 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 - }) - for _, priority := range priorities { - fmt.Printf("%s, Value %d\n", priority.Type, priority.Value) - } - fmt.Printf("\n") +// IsValid checks if the given types object is conformant to the specs +func (types *EIP712Types) IsValid() error { + for typeKey, typeArr := range (*types) { + for _, typeObj := range typeArr { + typeVal := typeObj["type"] + if typeKey == typeVal { + panic(fmt.Errorf("type %s cannot reference itself", typeVal)) + } - return priorities + firstChar := []rune(typeVal)[0] + if unicode.IsUpper(firstChar) { + if (*types)[typeVal] == nil { + return fmt.Errorf("referenced type %s is undefined", typeVal) + } + } else { + if !isStandardTypeStr(typeVal) { + if (*types)[typeVal] != nil { + return fmt.Errorf("custom type %s must be capitalized", typeVal) + } else { + return fmt.Errorf("unknown type %s", typeVal) + } + } + } + } + } + return nil } // IsValid checks if the given domain is valid, i.e. contains at least @@ -307,12 +603,63 @@ func (domain *EIP712Domain) IsValid() error { // 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, - "version": domain.Version, - "chainId": domain.Name, - "verifyingContract": domain.VerifyingContract, - "salt": domain.Salt, +func (domain *EIP712Domain) Map() EIP712Data { + dataMap := EIP712Data{ + "chainId": domain.ChainId, } + + if len(domain.Name) > 0 { + dataMap["name"] = domain.Name + } + + if len(domain.Version) > 0 { + dataMap["version"] = domain.Version + } + + if len(domain.VerifyingContract) > 0 { + dataMap["verifyingContract"] = domain.VerifyingContract + } + + if len(domain.Salt) > 0 { + dataMap["salt"] = domain.Salt + } + return dataMap } + +// Determines the content type and then recovers the address associated with the given sig +func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { + mediaType, _, err := mime.ParseMediaType(contentType) + if err != nil { + return common.Address{}, err + } + switch mediaType { + case TextPlain.Mime: + // 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 + // the address of: + // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") + // addr = ecrecover(hash, signature) + // + // Note, the signature must conform to the secp256k1 curve R, S and V values, where + // the V value must be be 27 or 28 for legacy reasons. + // + // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover + + if len(sig) != 65 { + return common.Address{}, fmt.Errorf("signature must be 65 bytes long") + } + if sig[64] != 27 && sig[64] != 28 { + return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") + } + sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 + hash, _ := signTextPlain(data) + rpk, err := crypto.SigToPub(hash, sig) + if err != nil { + return common.Address{}, err + } + return crypto.PubkeyToAddress(*rpk), nil + default: + return common.Address{}, fmt.Errorf("content type '%s' not implemented for ecRecover", contentType) + } +} \ No newline at end of file From 1a47367adc3209183072631b1f776651c0f3125a Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Tue, 23 Oct 2018 18:39:56 +0100 Subject: [PATCH 47/84] Ran goimports and gofmt --- cmd/clef/audit.log | 57 ++++++ signer/core/signed_data.go | 346 +++++++++++++++++++------------------ 2 files changed, 233 insertions(+), 170 deletions(-) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log index 667d0443b4f6..a38552de7fcd 100644 --- a/cmd/clef/audit.log +++ b/cmd/clef/audit.log @@ -146,3 +146,60 @@ t=2018-10-20T19:14:32+0100 lvl=info msg=SignTypedData api=signer type=response d t=2018-10-20T19:36:35+0100 lvl=info msg=Configured api=signer audit log=audit.log t=2018-10-20T19:36:40+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61953\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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-20T19:36:40+0100 lvl=info msg=SignTypedData api=signer type=response data=7951623617a41fec181fafc3555c0a494d01dce0408b6596964f5f50acba239e error=nil +t=2018-10-21T14:58:59+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T15:00:47+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T15:00:47+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52242\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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-21T15:05:18+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T15:07:18+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T15:07:20+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52313\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] Person:[map[name:name type:string] map[type:address name:wallet]]] 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-21T15:07:45+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T15:07:48+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52326\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-21T15:08:15+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T15:08:18+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52339\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:uint256 name:chainId] 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-21T15:08:46+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T15:08:51+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52351\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-21T15:09:58+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52360\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-21T15:10:10+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T15:10:23+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52371\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:string name:contents]]] 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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-21T15:11:27+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52373\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=deadbeef content-type=text/plain +t=2018-10-21T15:47:19+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T15:54:17+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:53039\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"Apache-HttpClient/4.5.5 (Java/1.8.0_152-release)\",\"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:[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-21T18:53:59+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T18:54:53+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T18:54:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54450\",\"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:[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-21T19:00:29+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:00:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54496\",\"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[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:[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-21T19:01:35+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:01:45+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54509\",\"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[type:Person name:from] map[type:Person name:to] map[type:string name:contents]] 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-21T19:06:53+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:07:33+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:07:56+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54595\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66D76e6A80DC7D46d7EC1b79b15Dfa34c3C6eF21 [chksum ok]" data="{Types:map[Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]] EIP712Domain:[map[name:name type:string] map[type:string name:version] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet 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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-21T19:09:56+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:09:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54620\",\"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[type:address name:verifyingContract]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[type:Person name:to] map[type:string name:contents]]] 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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-21T19:10:23+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:10:46+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54635\",\"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:[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-21T19:16:04+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:16:11+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54666\",\"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[type:uint256 name:chainId] 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[from:map[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-21T19:19:07+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:19:12+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54690\",\"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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-21T19:20:49+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:20:54+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54713\",\"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:[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-21T19:53:52+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:54:07+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54849\",\"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:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" +t=2018-10-21T19:55:14+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:55:19+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54863\",\"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[type:string name:version] map[name:chainId type:uint256] map[type:address name:verifyingContract]] 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:[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-21T19:56:13+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:56:18+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54874\",\"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[type:uint256 name:chainId] map[type:address name:verifyingContract]] 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:[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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-21T19:57:02+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:57:41+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:57:43+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54896\",\"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[type:string name:version] 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:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-21T19:58:03+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:58:04+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54908\",\"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[type:uint256 name:chainId] map[type:address name:verifyingContract]] 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:[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-21T19:58:39+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T19:58:44+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54920\",\"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:[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-21T20:00:09+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-21T20:00:34+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54933\",\"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[type:address name:verifyingContract]] 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:[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:03:51+0100 lvl=info msg=Configured api=signer audit log=audit.log +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]]}" diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index eef2c89b3c96..bf12a3ba4588 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -7,10 +7,7 @@ import ( "encoding/json" "errors" "fmt" - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto/sha3" - "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/common/math" "math/big" "mime" "reflect" @@ -18,18 +15,21 @@ import ( "strings" "unicode" - "github.com/PaulRBerg/basics/helpers" + "github.com/ethereum/go-ethereum/accounts" "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/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/sha3" + "github.com/ethereum/go-ethereum/rlp" ) type TypedData struct { - Types EIP712Types `json:"types"` - PrimaryType string `json:"primaryType"` - Domain EIP712Domain `json:"domain"` - Message EIP712Data `json:"message"` + Types EIP712Types `json:"types"` + PrimaryType string `json:"primaryType"` + Domain EIP712Domain `json:"domain"` + Message EIP712Data `json:"message"` } type EIP712Type []map[string]string @@ -44,20 +44,19 @@ 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 common.Address `json:"verifyingContract"` + Salt hexutil.Bytes `json:"salt"` } const ( - TypeArray = "array" - TypeAddress = "address" - TypeBool = "bool" - TypeBytes = "bytes" - TypeInt = "int" - TypeString = "string" + TypeAddress = "address" + TypeBool = "bool" + TypeBytes = "bytes" + TypeInt = "int" + TypeString = "string" ) // Sign receives a request and produces a signature @@ -215,57 +214,52 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd domainTypes := EIP712Types{ "EIP712Domain": typedData.Types["EIP712Domain"], } - domainSeparatorBytes := hashStruct(domainTypes, typedData.Domain.Map(), "EIP712Domain", 0) - domainSeparator := common.Bytes2Hex(domainSeparatorBytes) - //if err != nil { - // return nil, err - //} - delete(typedData.Types, "EIP712Domain") - typedDataHashBytes := hashStruct(typedData.Types, typedData.Message, typedData.PrimaryType, 0) - typedDataHash := common.Bytes2Hex(typedDataHashBytes) - //if err != nil { - // return nil, err - //} - - fmt.Println("domainSeparator", domainSeparator) - fmt.Println("typedDataHash", typedDataHash) + domainSeparatorBytes := typedData.hashStruct(domainTypes, typedData.Domain.Map(), "EIP712Domain", 0) + domainSeparator := common.BytesToHash(domainSeparatorBytes) - var buffer bytes.Buffer - buffer.WriteString("\x19\x01") - buffer.WriteString(domainSeparator) - buffer.WriteString(typedDataHash) - - sighash := crypto.Keccak256(buffer.Bytes()) - msg := fmt.Sprintf("\x19\\x%xEthereum Signed Message\n:%s%s", DataTyped.ByteVersion, domainSeparator, typedDataHash) - //req := &SignDataRequest{Rawdata: typedData, Message: msg, Hash: sighash, ContentType: DataTyped.Mime} + 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) - var req, err = api.determineSignatureFormat(contentType, data) + 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} signature, err := api.Sign(ctx, addr, req) if err != nil { api.UI.ShowError(err.Error()) return nil, err } - return signature, nil - - return crypto.Keccak256(buffer.Bytes()), nil } // hashStruct generates the following encoding for the given domain and message: // `encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01" β€– domainSeparator β€– hashStruct(message)` -func hashStruct(_types EIP712Types, data EIP712Data, dataType string, depth int) []byte { - helpers.PrintJson("hashStruct", map[string]interface{}{ - "depth": depth, - }) - - typeEncoding := encodeType(_types) +func (typedData *TypedData) hashStruct(_types EIP712Types, data EIP712Data, dataType string, depth int) []byte { + typeEncoding := typedData.encodeType(_types) typeHash := hex.EncodeToString(crypto.Keccak256(typeEncoding)) - dataEncoding := encodeData(_types, data, dataType, depth) + dataEncoding := typedData.encodeData(_types, data, dataType, depth) dataHash := hex.EncodeToString(crypto.Keccak256(dataEncoding)) var buffer bytes.Buffer @@ -278,6 +272,10 @@ func hashStruct(_types EIP712Types, data EIP712Data, dataType string, depth int) fmt.Printf("dataEncoding %s\n", common.Bytes2Hex(dataEncoding)) } + printJson("hashStruct", map[string]interface{}{ + "depth": depth, + "encoding": buffer.String(), + }) return encoding } @@ -285,12 +283,7 @@ func hashStruct(_types EIP712Types, data EIP712Data, dataType string, depth int) // `name β€– "(" β€– member₁ β€– "," β€– memberβ‚‚ β€– "," β€– … β€– memberβ‚™ ")"` // // each member is written as `type β€– " " β€– name` encodings cascade down and are sorted by name -func encodeType(_types EIP712Types) []byte { - helpers.PrintJson("encodeType", map[string]interface{}{ - "types": _types, - }) - //fmt.Printf("encodeType: types %v\n\n", types) - +func (typedData *TypedData) encodeType(_types EIP712Types) []byte { var priorities = make(map[string]uint) for key := range _types { priorities[key] = 0 @@ -313,8 +306,8 @@ func encodeType(_types EIP712Types) []byte { // Checks if referenced type has already been visited to optimise algo visited := func(arr []string, val string) bool { - for _, elem := range arr { - if elem == val { + for _, obj := range arr { + if obj == val { return true } } @@ -323,27 +316,22 @@ func encodeType(_types EIP712Types) []byte { for typeKey, typeArr := range _types { var typeValArr []string - for _, typeObj := range typeArr { typeVal := typeObj["type"] - //if typeKey == typeVal { - // panic(fmt.Errorf("type %s cannot reference itself", typeVal)) - //} - firstChar := []rune(typeVal)[0] - if unicode.IsUpper(firstChar) { - if _types[typeVal] != nil { - if !visited(typeValArr, typeVal) { - typeValArr = append(typeValArr, typeVal) - update(typeKey, typeVal) - } - } + // filtering the structs from the primitives + if _types[typeVal] != nil && !visited(typeValArr, typeVal) { + typeValArr = append(typeValArr, typeVal) + update(typeKey, typeVal) } } - typeValArr = []string{} } + if _types[typedData.PrimaryType] != nil { + priorities[typedData.PrimaryType] = math.MaxInt32 + } + sortedPriorities := sortByPriorityAndName(priorities) var buffer bytes.Buffer for _, priority := range sortedPriorities { @@ -364,44 +352,18 @@ func encodeType(_types EIP712Types) []byte { buffer.WriteString(")") } - return buffer.Bytes() -} - -// 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 + printJson("encodeType", map[string]interface{}{ + "types": _types, + "encoding": buffer.String(), }) - - //for _, priority := range priorities { - // fmt.Printf("%s, Value %d\n", priority.Type, priority.Value) - //} - //fmt.Printf("\n") - - return priorities + return buffer.Bytes() } // encodeData generates the following encoding: // `enc(value₁) β€– enc(valueβ‚‚) β€– … β€– enc(valueβ‚™)` // // each encoded member is 32-byte long -func encodeData(_types EIP712Types, data interface{}, dataType string, depth int) []byte { - helpers.PrintJson("encodeData", map[string]interface{}{ - "dataType": dataType, - "data": data, - "depth": depth, - }) - +func (typedData *TypedData) encodeData(_types EIP712Types, data interface{}, dataType string, depth int) []byte { var buffer bytes.Buffer // TODO regex @@ -412,7 +374,7 @@ func encodeData(_types EIP712Types, data interface{}, dataType string, depth int var arrayBuffer bytes.Buffer for obj := range arrayVal { - objEncoding := encodeData(_types, obj, dataType, depth+1) + objEncoding := typedData.encodeData(_types, obj, dataType, depth+1) arrayBuffer.Write(objEncoding) } @@ -428,10 +390,10 @@ func encodeData(_types EIP712Types, data interface{}, dataType string, depth int nextDataType := findNextDataType(_types, dataType, mapKey) if reflect.TypeOf(mapVal) == reflect.TypeOf(EIP712Data{}) { data := mapVal.(map[string]interface{}) - encoding := hashStruct(_types, data, nextDataType, depth+1) + encoding := typedData.hashStruct(_types, data, nextDataType, depth+1) buffer.Write(encoding) } else { - encoding := encodeData(_types, mapVal, nextDataType, depth+1) + encoding := typedData.encodeData(_types, mapVal, nextDataType, depth+1) buffer.Write(encoding) } } @@ -479,9 +441,72 @@ func encodeData(_types EIP712Types, data interface{}, dataType string, depth int break } + printJson("encodeData", map[string]interface{}{ + "dataType": dataType, + "data": data, + "depth": depth, + "encoding": buffer.String(), + }) return buffer.Bytes() } +// Determines the content type and then recovers the address associated with the given sig +func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { + mediaType, _, err := mime.ParseMediaType(contentType) + if err != nil { + return common.Address{}, err + } + switch mediaType { + case TextPlain.Mime: + // 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 + // the address of: + // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") + // addr = ecrecover(hash, signature) + // + // Note, the signature must conform to the secp256k1 curve R, S and V values, where + // the V value must be be 27 or 28 for legacy reasons. + // + // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover + + if len(sig) != 65 { + return common.Address{}, fmt.Errorf("signature must be 65 bytes long") + } + if sig[64] != 27 && sig[64] != 28 { + return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") + } + sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 + hash, _ := signTextPlain(data) + rpk, err := crypto.SigToPub(hash, sig) + if err != nil { + return common.Address{}, err + } + return crypto.PubkeyToAddress(*rpk), nil + default: + return common.Address{}, fmt.Errorf("content type '%s' not implemented for ecRecover", contentType) + } +} + +// 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 { @@ -499,10 +524,10 @@ func findNextDataType(_types EIP712Types, mapType string, mapKey string) string // UnmarshalJSON validates the input data func (typedData *TypedData) UnmarshalJSON(data []byte) error { type input struct { - Types EIP712Types `json:"types"` - PrimaryType string `json:"primaryType"` - Domain EIP712Domain `json:"domain"` - Message EIP712Data `json:"message"` + Types EIP712Types `json:"types"` + PrimaryType string `json:"primaryType"` + Domain EIP712Domain `json:"domain"` + Message EIP712Data `json:"message"` } var raw input @@ -539,29 +564,21 @@ func (typedData *TypedData) UnmarshalJSON(data []byte) error { return nil } -// isStandardType checks if the given type is a EIP712 conformant type -func isStandardTypeStr(typeStr string) bool { - standardTypes := []string{ - "array", - "address", - "boolean", - "bytes", - "string", - "struct", - "uint", +// Map is a helper function to generate a map version of the typed data +func (typedData *TypedData) Map() map[string]interface{} { + dataMap := map[string]interface{}{ + "Types": typedData.Types, + "Domain": typedData.Domain.Map(), + "PrimaryType": typedData.PrimaryType, + "Message": typedData.Message, } - for _, val := range standardTypes { - if strings.HasPrefix(typeStr, val) { - return true - } - } - return false -} + return dataMap +} // IsValid checks if the given types object is conformant to the specs func (types *EIP712Types) IsValid() error { - for typeKey, typeArr := range (*types) { + for typeKey, typeArr := range *types { for _, typeObj := range typeArr { typeVal := typeObj["type"] if typeKey == typeVal { @@ -587,6 +604,23 @@ func (types *EIP712Types) IsValid() error { return nil } +// isStandardType checks if the given type is a EIP712 conformant type +func isStandardTypeStr(typeStr string) bool { + standardTypes := []string{ + TypeAddress, + TypeBool, + TypeBytes, + TypeInt, + TypeString, + } + for _, val := range standardTypes { + if strings.HasPrefix(typeStr, val) || strings.Contains(typeStr, val) { + return true + } + } + return false +} + // IsValid checks if the given domain is valid, i.e. contains at least // the minimum viable keys and values func (domain *EIP712Domain) IsValid() error { @@ -601,11 +635,10 @@ func (domain *EIP712Domain) IsValid() error { return nil } -// Values is a helper function to return the values of a domain as a map -// with arbitrary values -func (domain *EIP712Domain) Map() EIP712Data { - dataMap := EIP712Data{ - "chainId": domain.ChainId, +// Map is a helper function to generate a map version of the domain +func (domain *EIP712Domain) Map() map[string]interface{} { + dataMap := map[string]interface{}{ + "chainId": domain.ChainId, } if len(domain.Name) > 0 { @@ -626,40 +659,13 @@ func (domain *EIP712Domain) Map() EIP712Data { return dataMap } -// Determines the content type and then recovers the address associated with the given sig -func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { - mediaType, _, err := mime.ParseMediaType(contentType) +// PrintJson will be removed +func printJson(label string, output map[string]interface{}) { + jsonVal, err := json.MarshalIndent(output, "", " ") if err != nil { - return common.Address{}, err + panic(err) } - switch mediaType { - case TextPlain.Mime: - // 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 - // the address of: - // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") - // addr = ecrecover(hash, signature) - // - // Note, the signature must conform to the secp256k1 curve R, S and V values, where - // the V value must be be 27 or 28 for legacy reasons. - // - // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover - - if len(sig) != 65 { - return common.Address{}, fmt.Errorf("signature must be 65 bytes long") - } - if sig[64] != 27 && sig[64] != 28 { - return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") - } - sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 - hash, _ := signTextPlain(data) - rpk, err := crypto.SigToPub(hash, sig) - if err != nil { - return common.Address{}, err - } - return crypto.PubkeyToAddress(*rpk), nil - default: - return common.Address{}, fmt.Errorf("content type '%s' not implemented for ecRecover", contentType) - } -} \ No newline at end of file + fmt.Printf("%s:", label) + fmt.Print(string(jsonVal)) + fmt.Print("\n\n") +} From 5f23386d76e91fe8745451c69cc960943ad64c0e Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Tue, 23 Oct 2018 20:17:36 +0100 Subject: [PATCH 48/84] Drafted first version of EIP-712, including tests --- cmd/clef/audit.log | 19 ++ cmd/clef/main.go | 4 +- signer/core/api_test.go | 66 ----- signer/core/signed_data.go | 412 +++++++++++++--------------- signer/core/signed_data_test.go | 457 ++------------------------------ 5 files changed, 231 insertions(+), 727 deletions(-) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log index a38552de7fcd..652dc04ae86a 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 648dd59306bf..3c872e628dbd 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -332,7 +332,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") //} @@ -558,7 +558,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 f1695feec935..ff68c0b4e1f0 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 bf12a3ba4588..2b74cb62eca2 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 - - // TODO regex - // handle arrays - if strings.Contains(dataType, "[]") { - arrayVal := data.([]interface{}) - dataType := "TODO" - - var arrayBuffer bytes.Buffer - for obj := range arrayVal { - objEncoding := typedData.encodeData(_types, obj, dataType, depth+1) - arrayBuffer.Write(objEncoding) +func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}) []byte { + encTypes := []string{} + encValues := []interface{}{} + + // Add typehash + encTypes = append(encTypes, "bytes32") + encValues = append(encValues, typedData.TypeHash(primaryType)) + + // Handle primitive values + handlePrimitiveValue := func(encType string, encValue interface{}) (string, interface{}) { + var primitiveEncType string + var primitiveEncValue interface{} + + 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 } - - 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) + return primitiveEncType, primitiveEncValue + } + + // 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) } - 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) - break - case TypeBool: - boolVal, _ := data.(bool) - var int64Val int64 - if boolVal { - int64Val = 1 - } - 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 + buffer := bytes.Buffer{} + for _, encValue := range encValues { + buffer.Write(bytesValueOf(encValue)) } - printJson("encodeData", map[string]interface{}{ - "dataType": dataType, - "data": data, - "depth": depth, - "encoding": buffer.String(), - }) - return buffer.Bytes() + 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 index 6761231c527d..80e59872b119 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -18,17 +18,14 @@ package core import ( "context" - "encoding/json" "fmt" - "math/big" - "testing" - "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" + "math/big" + "testing" ) -var typesStandard = Types{ +var typesStandard = EIP712Types{ "EIP712Domain": { { "name": "name", @@ -73,89 +70,17 @@ var typesStandard = Types{ }, } -var jsonTypedData = ` - { - "types": { - "EIP712Domain": [ - { - "name": "name", - "type": "string" - }, - { - "name": "version", - "type": "string" - }, - { - "name": "chainId", - "type": "uint256" - }, - { - "name": "verifyingContract", - "type": "address" - } - ], - "Person": [ - { - "name": "name", - "type": "string" - }, - { - "name": "test", - "type": "uint8" - }, - { - "name": "wallet", - "type": "address" - } - ], - "Mail": [ - { - "name": "from", - "type": "Person" - }, - { - "name": "to", - "type": "Person" - }, - { - "name": "contents", - "type": "string" - } - ] - }, - "primaryType": "Mail", - "domain": { - "name": "Ether Mail", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "from": { - "name": "Cow", - "test": 3, - "wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to": { - "name": "Bob", - "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents": "Hello, Bob!" - } - } -` - const primaryType = "Mail" -var domainStandard = TypedDataDomain{ +var domainStandard = EIP712Domain{ "Ether Mail", "1", big.NewInt(1), "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", - "", + nil, } -var messageStandard = map[string]interface{}{ +var dataStandard = map[string]interface{}{ "from": map[string]interface{}{ "name": "Cow", "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", @@ -168,10 +93,10 @@ var messageStandard = map[string]interface{}{ } var typedData = TypedData{ - Types: typesStandard, - PrimaryType: primaryType, - Domain: domainStandard, - Message: messageStandard, + typesStandard, + primaryType, + domainStandard, + dataStandard, } func TestSignData(t *testing.T) { @@ -188,7 +113,7 @@ func TestSignData(t *testing.T) { control <- "Y" control <- "wrongpassword" - signature, err := api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world"))) + signature, err := api.SignData(context.Background(), TextPlain.Mime, a, []byte("EHLO world")) if signature != nil { t.Errorf("Expected nil-data, got %x", signature) } @@ -196,7 +121,7 @@ func TestSignData(t *testing.T) { t.Errorf("Expected ErrLocked! %v", err) } control <- "No way" - signature, err = api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world"))) + signature, err = api.SignData(context.Background(), TextPlain.Mime, a, []byte("EHLO world")) if signature != nil { t.Errorf("Expected nil-data, got %x", signature) } @@ -206,7 +131,7 @@ func TestSignData(t *testing.T) { // text/plain control <- "Y" control <- "a_long_password" - signature, err = api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world"))) + signature, err = api.SignData(context.Background(), TextPlain.Mime, a, []byte("EHLO world")) if err != nil { t.Fatal(err) } @@ -223,378 +148,44 @@ func TestSignData(t *testing.T) { 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) { - hash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) - if err != nil { - t.Fatal(err) - } - mainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(hash)) + mainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(typedData.HashStruct(typedData.PrimaryType, typedData.Message))) if mainHash != "0xc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e" { - t.Errorf("Expected different hashStruct result (got %s)", mainHash) + t.Fatal(fmt.Errorf("hashStruct result %s is incorrect", mainHash)) } - hash, err = typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) - if err != nil { - t.Error(err) - } - domainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(hash)) + domainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(typedData.HashStruct("EIP712Domain", typedData.Domain.Map()))) if domainHash != "0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f" { - t.Errorf("Expected different domain hashStruct result (got %s)", domainHash) + 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.Errorf("Expected different encodeType result (got %s)", domainTypeEncoding) + 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.Errorf("Expected different encodeType result (got %s)", mailTypeEncoding) + 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.Errorf("Expected different typeHash result (got %s)", mailTypeHash) + t.Fatal(fmt.Errorf("typeHash result %s is incorrect", mailTypeHash)) } } func TestEncodeData(t *testing.T) { - hash, err := typedData.EncodeData(typedData.PrimaryType, typedData.Message, 0) - if err != nil { - t.Fatal(err) - } - dataEncoding := fmt.Sprintf("0x%s", common.Bytes2Hex(hash)) - if dataEncoding != "0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8cd54f074a4af31b4411ff6a60c9719dbd559c221c8ac3492d9d872b041d703d1b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8" { - t.Errorf("Expected different encodeData result (got %s)", dataEncoding) - } -} - -func TestMalformedData1(t *testing.T) { - // Verifies that malformed domain keys are properly caught: - //{ - // "name": "Ether Mail", - // "version": "1", - // "chainId": 1, - // "vxerifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - //} - var jsonTypedData = ` - { - "types": { - "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" - } - ] - }, - "primaryType": "Mail", - "domain": { - "name": "Ether Mail", - "version": "1", - "chainId": 1, - "vxerifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "from": { - "name": "Cow", - "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to": { - "name": "Bob", - "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents": "Hello, Bob!" - } - } -` - var malformedTypedData TypedData - err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) - if err != nil { - t.Fatalf("unmarshalling failed %v", err) - } - err = malformedTypedData.Validate() - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - _, err = malformedTypedData.HashStruct("EIP712Domain", malformedTypedData.Domain.Map()) - if err == nil || err.Error() != "provided data '' doesn't match type 'address'" { - t.Errorf("Expected `provided data '' doesn't match type 'address'`, got %v", err) - } -} - -func TestMalformedData2(t *testing.T) { - // Verifies that: - // 1. Mismatches between the given type and data, i.e. `Person` and - // the data item is a string, are properly caught: - //{ - // "name": "contents", - // "type": "Person" - //}, - //{ - // "contents": "Hello, Bob!" <-- string not "Person" - //} - // 2. Nonexistent types are properly caught: - //{ - // "name": "contents", - // "type": "Blahonga" - //} - jsonTypedData := ` - { - "types": { - "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": "Person" - } - ] - }, - "primaryType": "Mail", - "domain": { - "name": "Ether Mail", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "from": { - "name": "Cow", - "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to": { - "name": "Bob", - "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents": "Hello, Bob!" - } - } -` - var malformedTypedData TypedData - err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) - if err != nil { - t.Fatalf("unmarshalling failed %v", err) - } - err = malformedTypedData.Validate() - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) - if err.Error() != "provided data 'Hello, Bob!' doesn't match type 'Person'" { - t.Errorf("Expected `provided data 'Hello, Bob!' doesn't match type 'Person'`, got %v", err) - } - - malformedTypedData.Types["Mail"][2]["type"] = "Blahonga" - err = malformedTypedData.Validate() - if err == nil || err.Error() != "referenced type 'Blahonga' is undefined" { - t.Fatalf("Expected `referenced type 'Blahonga' is undefined`, got %v", err) - } - _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) - if err == nil || err.Error() != "unrecognized type 'Blahonga'" { - t.Errorf("Expected `unrecognized type 'Blahonga'`, got %v", err) - } -} - -func TestMalformedData3(t *testing.T) { - // Verifies several quirks - // 1. Using dynamic types and only validating the prefix: - //{ - // "name": "chainId", - // "type": "uint256 ... and now for something completely different" - //} - // 2. Extra data in message: - //{ - // "blahonga": "zonk bonk" - //} - jsonTypedData := ` - { - "types": { - "EIP712Domain": [ - { - "name": "name", - "type": "string" - }, - { - "name": "version", - "type": "string" - }, - { - "name": "chainId", - "type": "uint256 ... and now for something completely different" - }, - { - "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" - } - ] - }, - "primaryType": "Mail", - "domain": { - "name": "Ether Mail", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "from": { - "name": "Cow", - "wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to": { - "name": "Bob", - "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents": "Hello, Bob!" - } - } -` - var malformedTypedData TypedData - err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) - if err != nil { - t.Fatalf("unmarshalling failed %v", err) - } - err = malformedTypedData.Validate() - if err == nil || err.Error() != "unknown atomic type 'uint256 ... and now for something completely different'" { - t.Fatalf("Expected `unknown atomic type 'uint256 ... and now for something completely different'`, got %v", err) - } - - malformedTypedData.Types["EIP712Domain"][2]["type"] = "uint256" - malformedTypedData.Message["blahonga"] = "zonk bonk" - _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) - if err == nil || err.Error() != "there is extra data provided in the message" { - t.Errorf("Expected `there is extra data provided in the message`, got %v", err) - } -} - -func TestMalformedData4(t *testing.T) { - // Verifies data that doesn't fit into it: - //{ - // "test": 65536 <-- test defined as uint8 - //} - var malformedTypedData TypedData - err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) - if err != nil { - t.Fatalf("unmarshalling failed %v", err) - } - // Set test to something outside uint8 - (malformedTypedData.Message["from"]).(map[string]interface{})["test"] = 65536 - - err = malformedTypedData.Validate() - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - - _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) - if err == nil || err.Error() != "provided data '65536' doesn't match type 'uint8'" { - t.Fatalf("Expected `provided data '65536' doesn't match type 'uint8'`, got %v", err) - } - - (malformedTypedData.Message["from"]).(map[string]interface{})["test"] = big.NewInt(3) - (malformedTypedData.Message["to"]).(map[string]interface{})["test"] = big.NewInt(4) - - _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) - if err != nil { - t.Fatalf("Expected no err, got %v", err) + 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)) } } From e6bca1701460d7729c3d41791b265eaa44573814 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Wed, 24 Oct 2018 03:16:35 +0100 Subject: [PATCH 49/84] Temporarily switched to using common.Address in tests --- signer/core/signed_data.go | 10 +++++----- signer/core/signed_data_test.go | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 2b74cb62eca2..9795a8d3c662 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -61,7 +61,7 @@ type EIP712Domain struct { Name string `json:"name"` Version string `json:"version"` ChainId *big.Int `json:"chainId"` - VerifyingContract string `json:"verifyingContract"` + VerifyingContract common.Address `json:"verifyingContract"` Salt hexutil.Bytes `json:"salt"` } @@ -358,14 +358,14 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter switch encType { case "address": - primitiveEncType = "address" + primitiveEncType = "uint160" 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))) { + //foo := common.BytesToAddress([]byte(encValue.(string))) + //fmt.Println(foo) + for _, _byte := range encValue.(common.Address) { bytesValue = append(bytesValue, _byte) } primitiveEncValue = bytesValue diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 80e59872b119..1f2f0ce17a98 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -76,18 +76,18 @@ var domainStandard = EIP712Domain{ "Ether Mail", "1", big.NewInt(1), - "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + common.HexToAddress("0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"), nil, } var dataStandard = map[string]interface{}{ "from": map[string]interface{}{ "name": "Cow", - "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + "wallet": common.HexToAddress("0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"), }, "to": map[string]interface{}{ "name": "Bob", - "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + "wallet": common.HexToAddress("0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"), }, "contents": "Hello, Bob!", } From 2f018cea0d6bfe5118256d5e6668c50c7adcb71c Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Thu, 25 Oct 2018 14:41:04 +0200 Subject: [PATCH 50/84] Drafted text/validator and and rewritten []byte as hexutil.Bytes --- cmd/clef/audit.log | 23 +++++++++ signer/core/signed_data.go | 102 ++++++++++++++++++++----------------- 2 files changed, 79 insertions(+), 46 deletions(-) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log index 652dc04ae86a..45814ac8cfbd 100644 --- a/cmd/clef/audit.log +++ b/cmd/clef/audit.log @@ -222,3 +222,26 @@ t=2018-10-23T19:51:57+0100 lvl=info msg=Configured api=signer audit log=audit.lo 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!]}" +t=2018-10-25T13:47:17+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T13:47:54+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T13:50:09+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50039\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T13:51:00+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T13:51:07+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:51792\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T13:57:27+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T13:57:30+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:62734\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T14:01:29+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T14:01:30+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:53871\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608adeadbeef content-type=text/validator +t=2018-10-25T14:02:00+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T14:02:10+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:55131\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608adeadbeef content-type=text/validator +t=2018-10-25T14:13:15+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T14:13:16+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58361\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T14:13:53+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T14:14:04+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59928\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T14:14:39+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T14:14:43+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61106\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T14:21:56+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T14:22:59+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59342\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=60deadbeef content-type=text/validator +t=2018-10-25T14:22:59+0200 lvl=info msg=SignData api=signer type=response data= error="validator address and data undefined" +t=2018-10-25T14:23:05+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59342\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T14:23:31+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T14:23:55+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61180\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 9795a8d3c662..548438d355ae 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -39,6 +39,11 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) +type ValidatorData struct { + Address common.Address + Message hexutil.Bytes +} + type TypedData struct { Types EIP712Types `json:"types"` PrimaryType string `json:"primaryType"` @@ -77,7 +82,7 @@ const ( // Note, the produced signature conforms to the secp256k1 curve R, S and V values, // where the V value will be 27 or 28 for legacy reasons. -func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, req *SignDataRequest) ([]byte, error) { +func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, req *SignDataRequest) (hexutil.Bytes, error) { req.Address = addr req.Meta = MetadataFromContext(ctx) @@ -110,7 +115,7 @@ func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, re // // Different types of validation occur. func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { - var req, err = api.determineSignatureFormat(contentType, data) + var req, err = api.determineSignatureFormat(contentType, addr, data) if err != nil { return nil, err } @@ -125,7 +130,11 @@ func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr com } // Determines which signature method should be used based upon the mime type -func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil.Bytes) (*SignDataRequest, error) { +// In the cases where it matters ensure that the charset is handled. The charset +// resides in the 'params' returned as the second returnvalue from mime.ParseMediaType +// charset, ok := params["charset"] +// As it is now, we accept any charset and just treat it as 'raw'. +func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (*SignDataRequest, error) { var req *SignDataRequest mediaType, _, err := mime.ParseMediaType(contentType) if err != nil { @@ -135,18 +144,19 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil. switch mediaType { case TextValidator.Mime: // Data with an intended validator - sighash, msg := signTextWithValidator(data) + if len(data) < common.AddressLength { + return nil, errors.New("validator address and data undefined") + } + if len(data) == common.AddressLength { + return nil, errors.New("no data to sign") + } + sighash, msg := SignTextValidator(data) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} break case TextPlain.Mime: // Sign calculates an Ethereum ECDSA signature for: // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") - - // In the cases where it matters ensure that the charset is handled. The charset - // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType - // charset, ok := params["charset"] - // As it is now, we accept any charset and just treat it as 'raw'. - sighash, msg := signTextPlain(data) + sighash, msg := SignTextPlain(data) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} break case ApplicationClique.Mime: @@ -155,11 +165,11 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil. if err := rlp.DecodeBytes(data, header); err != nil { return nil, err } - sighash, err := signCliqueHeader(header) + sighash, err := SignCliqueHeader(header) if err != nil { return nil, err } - msg := fmt.Sprintf("Clique block %d [0x%x]", header.Number, header.Hash()) + msg := fmt.Sprintf("clique block %d [0x%x]", header.Number, header.Hash()) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} break default: @@ -169,23 +179,15 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil. } -// signTextPlain is a helper function that calculates a hash for the given message that can be -// safely used to calculate a signature from. -// -// The hash is calculated as -// keccak256("\x19${byteVersion}Ethereum Signed Message:\n"${message length}${message}). -// -// This gives context to the signed message and prevents signing of transactions. -func signTextPlain(data []byte) ([]byte, string) { - msg := fmt.Sprintf("\x19\\x%xEthereum Signed Message:\n%d%s", TextPlain.ByteVersion, len(data), data) - return crypto.Keccak256([]byte(msg)), msg -} - // signTextWithValidator signs the given message which can be further recovered // with the given validator. -func signTextWithValidator(data []byte) ([]byte, string) { - msg := "TODO" - return crypto.Keccak256([]byte(msg)), msg +// +// hash = keccak256("\x19\x00"${address}${data}). +func SignTextValidator(data hexutil.Bytes) (hexutil.Bytes, string) { + address := common.BytesToAddress(data[:common.AddressLength]) + message := data[common.AddressLength:len(data)-1] + hash := fmt.Sprintf("\x19\x00:%x%s", address, string(message)) + return crypto.Keccak256(hexutil.Bytes(hash)), hash } // signCliqueHeader returns the hash which is used as input for the proof-of-authority @@ -195,7 +197,7 @@ func signTextWithValidator(data []byte) ([]byte, string) { // The method requires the extra data to be at least 65 bytes -- the original implementation // in clique.go panics if this is the case, thus it's been reimplemented here to avoid the panic // and simply return an error instead -func signCliqueHeader(header *types.Header) (hexutil.Bytes, error) { +func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { hash := common.Hash{} if len(header.Extra) < 65 { return hash.Bytes(), fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) @@ -222,6 +224,18 @@ func signCliqueHeader(header *types.Header) (hexutil.Bytes, error) { return hash.Bytes(), nil } +// 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 +// signing of transactions. +// +// hash = keccak256("\x19$Ethereum Signed Message:\n"${message length}${message}). +func SignTextPlain(data hexutil.Bytes) (hexutil.Bytes, string) { + // The letter `E` is \x45 in hex, retrofitting + // https://github.com/ethereum/go-ethereum/pull/2940/commits + hash := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data)) + return crypto.Keccak256(hexutil.Bytes(hash)), hash +} + // 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) { @@ -252,12 +266,11 @@ 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(primaryType string, data EIP712Data) []byte { +func (typedData *TypedData) HashStruct(primaryType string, data EIP712Data) hexutil.Bytes { 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 { includes := func(arr []string, str string) bool { for _, obj := range arr { @@ -289,7 +302,7 @@ func (typedData *TypedData) Dependencies(primaryType string, found []string) []s // `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 { +func (typedData *TypedData) EncodeType(primaryType string) hexutil.Bytes { // Get dependencies primary first, then alphabetical deps := typedData.Dependencies(primaryType, []string{}) for i, dep := range deps { @@ -317,33 +330,33 @@ func (typedData *TypedData) EncodeType(primaryType string) []byte { return buffer.Bytes() } -func (typedData *TypedData) TypeHash(primaryType string) []byte { +func (typedData *TypedData) TypeHash(primaryType string) hexutil.Bytes { return crypto.Keccak256(typedData.EncodeType(primaryType)) } -func bytesValueOf(_interface interface{}) []byte { - bytesVal, ok := _interface.([]byte) +func bytesValueOf(_interface interface{}) hexutil.Bytes { + bytesVal, ok := _interface.(hexutil.Bytes) if ok { return bytesVal } switch reflect.TypeOf(_interface) { case reflect.TypeOf(string("")): - return []byte(_interface.(string)) + return hexutil.Bytes(_interface.(string)) break default: break } panic(fmt.Errorf("unrecognized interface %v", _interface)) - return []byte{} + return hexutil.Bytes{} } // encodeData generates the following encoding: // `enc(value₁) β€– enc(valueβ‚‚) β€– … β€– enc(valueβ‚™)` // // each encoded member is 32-byte long -func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}) []byte { +func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}) hexutil.Bytes { encTypes := []string{} encValues := []interface{}{} @@ -359,12 +372,10 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter switch encType { case "address": primitiveEncType = "uint160" - bytesValue := []byte{} + bytesValue := hexutil.Bytes{} for i := 0; i < 12; i++ { bytesValue = append(bytesValue, 0) } - //foo := common.BytesToAddress([]byte(encValue.(string))) - //fmt.Println(foo) for _, _byte := range encValue.(common.Address) { bytesValue = append(bytesValue, _byte) } @@ -387,11 +398,11 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter encTypes = append(encTypes, "bytes32") sizeStr := strings.TrimPrefix(encType, "bytes") size, _ := strconv.Atoi(sizeStr) - bytesValue := []byte{} + bytesValue := hexutil.Bytes{} for i := 0; i < 32-size; i++ { bytesValue = append(bytesValue, 0) } - for _, _byte := range encValue.([]byte) { + for _, _byte := range encValue.(hexutil.Bytes) { bytesValue = append(bytesValue, _byte) } primitiveEncValue = bytesValue @@ -461,7 +472,6 @@ func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data he // the V value must be be 27 or 28 for legacy reasons. // // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover - if len(sig) != 65 { return common.Address{}, fmt.Errorf("signature must be 65 bytes long") } @@ -469,7 +479,7 @@ func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data he return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") } sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 - hash, _ := signTextPlain(data) + hash, _ := SignTextPlain(data) rpk, err := crypto.SigToPub(hash, sig) if err != nil { return common.Address{}, err @@ -481,7 +491,7 @@ func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data he } // UnmarshalJSON validates the input data -func (typedData *TypedData) UnmarshalJSON(data []byte) error { +func (typedData *TypedData) UnmarshalJSON(data hexutil.Bytes) error { type input struct { Types EIP712Types `json:"types"` PrimaryType string `json:"primaryType"` From 798866bac0951008017af4a81e1596068af2ce6f Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sat, 27 Oct 2018 10:42:18 +0200 Subject: [PATCH 51/84] Solved stringified address encoding issue --- accounts/accounts.go | 2 +- cmd/clef/audit.log | 9 +++++ signer/core/signed_data.go | 68 +++++++++++++++++++-------------- signer/core/signed_data_test.go | 19 +++++---- 4 files changed, 58 insertions(+), 40 deletions(-) diff --git a/accounts/accounts.go b/accounts/accounts.go index cb1eae281587..a0675ab7a609 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -109,7 +109,7 @@ type Wallet interface { // a password to decrypt the account, or a PIN code to verify the transaction), // an AuthNeededError instance will be returned, containing infos for the user // about which fields or actions are needed. The user may retry by providing - // the needed details via SignTxWithPassphrase, or by other means (e.g. unlock + // the needed details via SignHashWithPassphraseSignTxWithPassphrase, or by other means (e.g. unlock // the account in a keystore). SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log index 45814ac8cfbd..85b2cc1f8d8d 100644 --- a/cmd/clef/audit.log +++ b/cmd/clef/audit.log @@ -245,3 +245,12 @@ t=2018-10-25T14:22:59+0200 lvl=info msg=SignData api=signer type=response data t=2018-10-25T14:23:05+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59342\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator t=2018-10-25T14:23:31+0200 lvl=info msg=Configured api=signer audit log=audit.log t=2018-10-25T14:23:55+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61180\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T20:12:47+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T20:13:00+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59607\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T20:34:51+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T20:34:53+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:62187\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T20:35:38+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T20:35:43+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:62300\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-25T20:41:50+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-25T20:42:06+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:63054\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-26T10:33:40+0200 lvl=info msg=Configured api=signer audit log=audit.log diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 548438d355ae..eb5df4d8939b 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -63,11 +63,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 ( @@ -76,6 +76,7 @@ const ( TypeBytes = "bytes" TypeInt = "int" TypeString = "string" + TypeUint = "uint" ) // Sign receives a request and produces a signature @@ -151,6 +152,7 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M return nil, errors.New("no data to sign") } sighash, msg := SignTextValidator(data) + fmt.Printf("%s", sighash) req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} break case TextPlain.Mime: @@ -185,8 +187,8 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M // hash = keccak256("\x19\x00"${address}${data}). func SignTextValidator(data hexutil.Bytes) (hexutil.Bytes, string) { address := common.BytesToAddress(data[:common.AddressLength]) - message := data[common.AddressLength:len(data)-1] - hash := fmt.Sprintf("\x19\x00:%x%s", address, string(message)) + message := data[common.AddressLength:] + hash := fmt.Sprintf("\x19\x00%s%s", address, string(message)) return crypto.Keccak256(hexutil.Bytes(hash)), hash } @@ -335,20 +337,23 @@ func (typedData *TypedData) TypeHash(primaryType string) hexutil.Bytes { } func bytesValueOf(_interface interface{}) hexutil.Bytes { - bytesVal, ok := _interface.(hexutil.Bytes) + bytesValue, ok := _interface.(hexutil.Bytes) if ok { - return bytesVal + return bytesValue } switch reflect.TypeOf(_interface) { + case reflect.TypeOf(hexutil.Bytes{}): + return _interface.(hexutil.Bytes) + case reflect.TypeOf([]uint8{}): + return _interface.([]uint8) case reflect.TypeOf(string("")): return hexutil.Bytes(_interface.(string)) - break default: break } - panic(fmt.Errorf("unrecognized interface %v", _interface)) + panic(fmt.Errorf("unrecognized interface type %T", _interface)) return hexutil.Bytes{} } @@ -376,7 +381,7 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter for i := 0; i < 12; i++ { bytesValue = append(bytesValue, 0) } - for _, _byte := range encValue.(common.Address) { + for _, _byte := range common.HexToAddress(encValue.(string)) { bytesValue = append(bytesValue, _byte) } primitiveEncValue = bytesValue @@ -575,19 +580,35 @@ func (types *EIP712Types) IsValid() error { } // isStandardType checks if the given type is a EIP712 conformant type -func isStandardTypeStr(typeStr string) bool { - standardTypes := []string{ +func isStandardTypeStr(encType string) bool { + // Atomic types + for _, standardType := range []string{ TypeAddress, TypeBool, TypeBytes, - TypeInt, TypeString, + } { + if standardType == encType { + return true + } } - for _, val := range standardTypes { - if strings.HasPrefix(typeStr, val) || strings.Contains(typeStr, val) { + + // Dynamic types + for _, standardType := range []string { + TypeBytes, + TypeInt, + TypeUint, + } { + if strings.HasPrefix(encType, standardType) { return true } } + + // Reference types + if encType[len(encType)-1] == ']' { + return true + } + return false } @@ -627,15 +648,4 @@ func (domain *EIP712Domain) Map() map[string]interface{} { dataMap["salt"] = domain.Salt } return dataMap -} - -// PrintJson will be removed -func printJson(label string, output map[string]interface{}) { - jsonVal, err := json.MarshalIndent(output, "", " ") - if err != nil { - panic(err) - } - fmt.Printf("%s:", label) - fmt.Print(string(jsonVal)) - fmt.Print("\n\n") -} +} \ No newline at end of file diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 1f2f0ce17a98..f25699bb2d91 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -76,18 +76,18 @@ var domainStandard = EIP712Domain{ "Ether Mail", "1", big.NewInt(1), - common.HexToAddress("0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"), + "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", nil, } var dataStandard = map[string]interface{}{ "from": map[string]interface{}{ "name": "Cow", - "wallet": common.HexToAddress("0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"), + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", }, "to": map[string]interface{}{ "name": "Bob", - "wallet": common.HexToAddress("0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"), + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", }, "contents": "Hello, Bob!", } @@ -148,37 +148,36 @@ func TestSignData(t *testing.T) { 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)) + t.Errorf("Expected different hashStruct result (got %s)", 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)) + t.Errorf("Expected different hashStruct result (got %s)", 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)) + t.Errorf("Expected different encodeType result (got %s)", 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)) + t.Errorf("Expected different encodeType result (got %s)", 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)) + t.Errorf("Expected different typeHash result (got %s)", mailTypeHash) } } @@ -186,6 +185,6 @@ 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)) + t.Errorf("Expected different encodeData result (got %s)", dataEncoding) } } From 6c68ed1b1769ffdca616a58065a75e41163d1280 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Fri, 2 Nov 2018 10:19:25 +0100 Subject: [PATCH 52/84] Changed the property type required by signData from bytes to interface{} --- cmd/clef/audit.log | 148 +++++++++++++ cmd/clef/main.go | 4 +- signer/core/api.go | 34 +-- signer/core/auditlog.go | 14 +- signer/core/signed_data.go | 368 ++++++++++++++++++++------------ signer/core/signed_data_test.go | 28 ++- 6 files changed, 408 insertions(+), 188 deletions(-) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log index 85b2cc1f8d8d..0ce97ab5fe16 100644 --- a/cmd/clef/audit.log +++ b/cmd/clef/audit.log @@ -254,3 +254,151 @@ t=2018-10-25T20:35:43+0200 lvl=info msg=SignData api=signer type=request metad t=2018-10-25T20:41:50+0200 lvl=info msg=Configured api=signer audit log=audit.log t=2018-10-25T20:42:06+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:63054\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator t=2018-10-26T10:33:40+0200 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T11:29:41+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T11:30:16+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T11:30:28+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:64014\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-31T11:39:42+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T11:48:29+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T11:48:37+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:64737\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-31T11:50:49+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T11:50:59+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:64820\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-31T11:51:44+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T11:52:05+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:64849\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator +t=2018-10-31T14:46:14+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T14:47:22+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:51211\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe validator:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator +t=2018-10-31T14:47:22+0100 lvl=info msg=SignData api=signer type=response data= error=nil +t=2018-10-31T15:45:57+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T15:46:31+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:51638\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[validator:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T15:46:31+0100 lvl=info msg=SignData api=signer type=response data= error="validator data not constructed correctly" +t=2018-10-31T15:46:57+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T15:47:00+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:51654\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[validator:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T15:47:00+0100 lvl=info msg=SignData api=signer type=response data= error="validator data not constructed correctly" +t=2018-10-31T16:24:12+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:24:30+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:29:29+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:51956\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[validator:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T16:35:14+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:35:19+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52038\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T16:35:19+0100 lvl=info msg=SignData api=signer type=response data= error="message is undefined" +t=2018-10-31T16:35:41+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:36:12+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52052\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T16:37:15+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:37:18+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52065\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator +t=2018-10-31T16:38:10+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:38:13+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52080\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0x1c]" content-type=text/validator +t=2018-10-31T16:38:39+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:38:42+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52097\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0x1c]" content-type=text/validator +t=2018-10-31T16:38:57+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:39:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52109\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator +t=2018-10-31T16:47:12+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:47:22+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52263\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T16:50:27+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:50:52+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:51:19+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:52:19+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T16:53:07+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52394\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T17:01:45+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T17:01:56+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T17:02:14+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52600\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T17:03:31+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T17:03:35+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52624\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T17:04:19+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T17:04:24+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52643\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator +t=2018-10-31T17:05:01+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T17:05:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52657\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T17:11:09+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T17:11:13+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52695\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T17:12:22+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T17:12:38+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T17:12:38+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52724\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T19:15:08+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:16:59+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:49906\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T19:28:45+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:29:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50208\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T19:29:21+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:29:32+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50219\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T19:35:37+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:35:38+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50288\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T19:36:22+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:36:29+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50310\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator +t=2018-10-31T19:36:56+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:36:59+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50328\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator +t=2018-10-31T19:40:24+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:40:26+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50369\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator +t=2018-10-31T19:41:50+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:43:03+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50469\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T19:43:58+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:44:14+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50485\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xdeadbeef]" content-type=text/validator +t=2018-10-31T19:44:30+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:44:32+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50501\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T19:47:37+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:47:56+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50552\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-10-31T19:48:10+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-31T19:48:39+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50567\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator +t=2018-11-01T19:16:25+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T19:16:31+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57757\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator +t=2018-11-01T21:11:47+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:13:52+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59254\",\"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-01T21:14:40+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:14:48+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59317\",\"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-01T21:16:00+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:16:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59339\",\"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-01T21:18:49+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:18:50+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59375\",\"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-01T21:26:42+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:26:55+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59485\",\"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-01T21:34:20+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:34:24+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59590\",\"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-01T21:37:05+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:37:48+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59652\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0xd090B1504cC9341BC18cE5d34D750545bb515FdC]" content-type=text/validator +t=2018-11-01T21:43:00+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:43:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59721\",\"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-01T21:44:59+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:45:01+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59759\",\"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-01T21:45:19+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:45:21+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59771\",\"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-01T21:46:31+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:46:33+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59786\",\"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-01T21:48:12+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:48:13+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59830\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain +t=2018-11-01T21:48:47+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:49:03+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:49:03+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59851\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain +t=2018-11-01T21:50:58+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:51:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59872\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain +t=2018-11-01T21:52:07+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:52:09+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59892\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain +t=2018-11-01T21:52:21+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:52:22+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59904\",\"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-01T21:53:12+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:53:14+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59919\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain +t=2018-11-01T21:53:51+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:54:08+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59957\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain +t=2018-11-01T21:54:21+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T21:54:26+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59971\",\"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-01T21:59:18+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:00:23+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60071\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain +t=2018-11-01T22:00:57+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:01:04+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60083\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain +t=2018-11-01T22:06:16+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:06:20+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60136\",\"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:07:41+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:07:42+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60170\",\"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:08:13+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:09:25+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60229\",\"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:11:52+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:11:53+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60257\",\"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:13:09+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:13:33+0100 lvl=info msg=EcRecover api=signer type=request metadata="{\"remote\":\"127.0.0.1:60277\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" data=cafebabe sig=d7cabcefb177b419f8c4d124cdf3487dfdf2f8e4cceb9bad5dfb8707130c9b7b394e0e7057335ce459145ff6d6b5bd52242c54e11ae5934637358930f2f6d0651b content-type=text/plain +t=2018-11-01T22:13:33+0100 lvl=info msg=EcRecover api=signer type=response address=0x66D76e6A80DC7D46d7EC1b79b15Dfa34c3C6eF21 error=nil +t=2018-11-01T22:13:44+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60277\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain +t=2018-11-01T22:15:03+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:15:24+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60301\",\"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:15:36+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:15:39+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60319\",\"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:05+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:23:20+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-11-01T22:23:27+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60494\",\"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:54+0100 lvl=info msg=Configured api=signer audit log=audit.log +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: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 diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 3c872e628dbd..be192090c983 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -668,8 +668,8 @@ func testExternalUI(api *core.SignerAPI) { checkErr("SignTransaction", err) _, err = api.SignData(ctx, "text/plain", common.MixedcaseAddress{}, common.Hex2Bytes("01020304")) checkErr("SignData", err) - _, err = api.SignTypedData(ctx, common.MixedcaseAddress{}, core.TypedData{}) - checkErr("SignTypedData", err) + //_, err = api.SignTypedData(ctx, common.MixedcaseAddress{}, core.TypedData{}) + //checkErr("SignTypedData", err) _, err = api.List(ctx) checkErr("List", err) _, err = api.New(ctx) diff --git a/signer/core/api.go b/signer/core/api.go index 141c1df9d858..49039373725e 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -47,9 +47,9 @@ type ExternalAPI interface { // SignTransaction request to sign the specified transaction SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) // SignData - request to sign the given data (plus prefix) - SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (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(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(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) // Export - request to export an account @@ -108,30 +108,6 @@ type Metadata struct { Origin string `json:"Origin"` } -type SigFormat struct { - Mime string - ByteVersion byte -} - -var ( - TextValidator = SigFormat{ - "text/validator", - 0x00, - } - DataTyped = SigFormat{ - "data/typed", - 0x01, - } - ApplicationClique = SigFormat{ - "application/clique", - 0x02, - } - TextPlain = SigFormat{ - "text/plain", - 0x45, - } -) - // MetadataFromContext extracts Metadata from a given context.Context func MetadataFromContext(ctx context.Context) Metadata { m := Metadata{"NA", "NA", "NA", "", ""} // batman @@ -199,11 +175,7 @@ type ( SignDataRequest struct { ContentType string `json:"content_type"` Address common.MixedcaseAddress `json:"address"` -<<<<<<< HEAD - Rawdata interface{} `json:"raw_data"` -======= - Rawdata hexutil.Bytes `json:"raw_data"` ->>>>>>> 834cf03b0... Named functions and defined a basic EIP191 content type list + Rawdata interface{} `json:"raw_data"` Message string `json:"message"` Hash hexutil.Bytes `json:"hash"` Meta Metadata `json:"meta"` diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go index 0762415f5421..b9769b16e91c 100644 --- a/signer/core/auditlog.go +++ b/signer/core/auditlog.go @@ -70,13 +70,13 @@ func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr com return b, e } -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(), - "addr", addr.String(), "data", data) - b, e := l.api.SignTypedData(ctx, addr, data) - l.log.Info("SignTypedData", "type", "response", "data", common.Bytes2Hex(b), "error", e) - return b, e -} +//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(), +// "addr", addr.String(), "data", data) +// b, e := l.api.SignTypedData(ctx, addr, data) +// l.log.Info("SignTypedData", "type", "response", "data", common.Bytes2Hex(b), "error", e) +// return b, e +//} func (l *AuditLogger) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { l.log.Info("EcRecover", "type", "request", "metadata", MetadataFromContext(ctx).String(), diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index eb5df4d8939b..f86845ed2f28 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -19,7 +19,6 @@ package core import ( "bytes" "context" - "encoding/json" "errors" "fmt" "math/big" @@ -39,6 +38,30 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) +type SigFormat struct { + Mime string + ByteVersion byte +} + +var ( + TextValidator = SigFormat{ + "text/validator", + 0x00, + } + DataTyped = SigFormat{ + "data/typed", + 0x01, + } + ApplicationClique = SigFormat{ + "application/clique", + 0x02, + } + TextPlain = SigFormat{ + "text/plain", + 0x45, + } +) + type ValidatorData struct { Address common.Address Message hexutil.Bytes @@ -67,7 +90,7 @@ type EIP712Domain struct { Version string `json:"version"` ChainId *big.Int `json:"chainId"` VerifyingContract string `json:"verifyingContract"` - Salt hexutil.Bytes `json:"salt"` + Salt string `json:"salt"` } const ( @@ -115,8 +138,8 @@ func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, re // depending on the content-type specified. // // Different types of validation occur. -func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { - var req, err = api.determineSignatureFormat(contentType, addr, data) +func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error) { + var req, err= api.determineSignatureFormat(contentType, addr, data) if err != nil { return nil, err } @@ -135,7 +158,7 @@ func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr com // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType // charset, ok := params["charset"] // As it is now, we accept any charset and just treat it as 'raw'. -func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (*SignDataRequest, error) { +func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.MixedcaseAddress, data interface{}) (*SignDataRequest, error) { var req *SignDataRequest mediaType, _, err := mime.ParseMediaType(contentType) if err != nil { @@ -145,26 +168,32 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M switch mediaType { case TextValidator.Mime: // Data with an intended validator - if len(data) < common.AddressLength { - return nil, errors.New("validator address and data undefined") - } - if len(data) == common.AddressLength { - return nil, errors.New("no data to sign") + validatorData, err := UnmarshalValidatorData(data) + if err != nil { + return nil, err } - sighash, msg := SignTextValidator(data) - fmt.Printf("%s", sighash) - req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + sighash, msg := SignTextValidator(validatorData) + fmt.Printf("sighash:%s", sighash) + req = &SignDataRequest{Rawdata: validatorData, Message: msg, Hash: sighash, ContentType: mediaType} break - case TextPlain.Mime: - // Sign calculates an Ethereum ECDSA signature for: - // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") - sighash, msg := SignTextPlain(data) - req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + 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: // Clique is the Ethereum PoA standard + cliqueData, err := hexutil.Decode(data.(string)) + if err != nil { + return nil, err + } header := &types.Header{} - if err := rlp.DecodeBytes(data, header); err != nil { + if err := rlp.DecodeBytes(cliqueData, header); err != nil { return nil, err } sighash, err := SignCliqueHeader(header) @@ -172,7 +201,17 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M return nil, err } msg := fmt.Sprintf("clique block %d [0x%x]", header.Number, header.Hash()) - req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + req = &SignDataRequest{Rawdata: cliqueData, Message: msg, Hash: sighash, ContentType: mediaType} + break + case TextPlain.Mime: + // Calculates an Ethereum ECDSA signature for: + // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") + plainData, err := hexutil.Decode(data.(string)) + if err != nil { + return nil, err + } + sighash, msg := SignTextPlain(plainData) + req = &SignDataRequest{Rawdata: plainData, Message: msg, Hash: sighash, ContentType: mediaType} break default: return nil, fmt.Errorf("content type '%s' not implemented for signing", contentType) @@ -181,18 +220,17 @@ 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. // // hash = keccak256("\x19\x00"${address}${data}). -func SignTextValidator(data hexutil.Bytes) (hexutil.Bytes, string) { - address := common.BytesToAddress(data[:common.AddressLength]) - message := data[common.AddressLength:] - hash := fmt.Sprintf("\x19\x00%s%s", address, string(message)) - return crypto.Keccak256(hexutil.Bytes(hash)), hash +func SignTextValidator(validatorData ValidatorData) (hexutil.Bytes, string) { + msg := fmt.Sprintf("\x19\x00%s%s", string(validatorData.Address.Bytes()), string(validatorData.Message)) + fmt.Printf("SignTextValidator:%s\n", msg) + return crypto.Keccak256([]byte(msg)), msg } -// signCliqueHeader returns the hash which is used as input for the proof-of-authority +// SignCliqueHeader returns the hash which is used as input for the proof-of-authority // signing. It is the hash of the entire header apart from the 65 byte signature // contained at the end of the extra data. // @@ -226,7 +264,7 @@ func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { return hash.Bytes(), nil } -// 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 // signing of transactions. // @@ -234,39 +272,48 @@ func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { func SignTextPlain(data hexutil.Bytes) (hexutil.Bytes, string) { // The letter `E` is \x45 in hex, retrofitting // https://github.com/ethereum/go-ethereum/pull/2940/commits - hash := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data)) - return crypto.Keccak256(hexutil.Bytes(hash)), hash + msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) + return crypto.Keccak256([]byte(msg)), msg } -// SignTypedData signs EIP-712 conformant typed data +// SignDataTyped 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) { +func SignDataTyped(typedData TypedData) (hexutil.Bytes, string) { 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 - } - buffer := bytes.Buffer{} - buffer.WriteString("\x19") - 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()) - return nil, err - } - return signature, nil + msg := fmt.Sprintf("\x19\x01%s%s", common.Bytes2Hex(domainSeparator), common.Bytes2Hex(typedDataHash)) + return crypto.Keccak256(common.Hex2Bytes(msg)), msg } -// hashStruct generates the following encoding for the given domain and message: +// 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) { +// 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 +// } +// buffer := bytes.Buffer{} +// buffer.WriteString("\x19") +// 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()) +// return nil, err +// } +// return signature, nil +//} + +// HashStruct generates the following encoding for the given domain and message: // `encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01" β€– domainSeparator β€– hashStruct(message)` func (typedData *TypedData) HashStruct(primaryType string, data EIP712Data) hexutil.Bytes { return crypto.Keccak256(typedData.EncodeData(primaryType, data)) @@ -336,28 +383,7 @@ func (typedData *TypedData) TypeHash(primaryType string) hexutil.Bytes { return crypto.Keccak256(typedData.EncodeType(primaryType)) } -func bytesValueOf(_interface interface{}) hexutil.Bytes { - bytesValue, ok := _interface.(hexutil.Bytes) - if ok { - return bytesValue - } - - switch reflect.TypeOf(_interface) { - case reflect.TypeOf(hexutil.Bytes{}): - return _interface.(hexutil.Bytes) - case reflect.TypeOf([]uint8{}): - return _interface.([]uint8) - case reflect.TypeOf(string("")): - return hexutil.Bytes(_interface.(string)) - default: - break - } - - panic(fmt.Errorf("unrecognized interface type %T", _interface)) - return hexutil.Bytes{} -} - -// encodeData generates the following encoding: +// EncodeData generates the following encoding: // `enc(value₁) β€– enc(valueβ‚‚) β€– … β€– enc(valueβ‚™)` // // each encoded member is 32-byte long @@ -458,93 +484,159 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter 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 +func bytesValueOf(_interface interface{}) hexutil.Bytes { + bytesValue, ok := _interface.(hexutil.Bytes) + if ok { + return bytesValue + } + + switch reflect.TypeOf(_interface) { + case reflect.TypeOf(hexutil.Bytes{}): + return _interface.(hexutil.Bytes) + case reflect.TypeOf([]uint8{}): + return _interface.([]uint8) + case reflect.TypeOf(string("")): + return common.Hex2Bytes(_interface.(string)) + default: + break + } + + panic(fmt.Errorf("unrecognized interface type %T", _interface)) + return hexutil.Bytes{} +} + +// EcRecover recovers the address associated with the given sig. +// Only compatible with `text/plain` func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { - mediaType, _, err := mime.ParseMediaType(contentType) + // 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 + // the address of: + // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") + // addr = ecrecover(hash, signature) + // + // Note, the signature must conform to the secp256k1 curve R, S and V values, where + // the V value must be be 27 or 28 for legacy reasons. + // + // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover + if len(sig) != 65 { + return common.Address{}, fmt.Errorf("signature must be 65 bytes long") + } + if sig[64] != 27 && sig[64] != 28 { + return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") + } + sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 + hash, _ := SignTextPlain(data) + rpk, err := crypto.SigToPub(hash, sig) if err != nil { return common.Address{}, err } - switch mediaType { - case TextPlain.Mime: - // 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 - // the address of: - // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") - // addr = ecrecover(hash, signature) - // - // Note, the signature must conform to the secp256k1 curve R, S and V values, where - // the V value must be be 27 or 28 for legacy reasons. - // - // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover - if len(sig) != 65 { - return common.Address{}, fmt.Errorf("signature must be 65 bytes long") - } - if sig[64] != 27 && sig[64] != 28 { - return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") - } - sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 - hash, _ := SignTextPlain(data) - rpk, err := crypto.SigToPub(hash, sig) - if err != nil { - return common.Address{}, err - } - return crypto.PubkeyToAddress(*rpk), nil - default: - return common.Address{}, fmt.Errorf("content type '%s' not implemented for ecRecover", contentType) - } + return crypto.PubkeyToAddress(*rpk), nil } -// UnmarshalJSON validates the input data -func (typedData *TypedData) UnmarshalJSON(data hexutil.Bytes) error { - type input struct { - Types EIP712Types `json:"types"` - PrimaryType string `json:"primaryType"` - Domain EIP712Domain `json:"domain"` - Message EIP712Data `json:"message"` +// UnmarshalValidatorData converts the bytes input to typed data +func UnmarshalValidatorData(data interface{}) (ValidatorData, error) { + raw := data.(map[string]interface{}) + + addr, ok := raw["address"].(string) + addrBytes, err := hexutil.Decode(addr) + if err != nil { + return ValidatorData{}, err + } + if !ok || len(addrBytes) == 0 { + return ValidatorData{}, errors.New("validator address is undefined") } - var raw input - if err := json.Unmarshal(data, &raw); err != nil { - return err + message, ok := raw["message"].(string) + messageBytes, err := hexutil.Decode(message) + if err != nil { + return ValidatorData{}, err } + if !ok || len(messageBytes) == 0 { + return ValidatorData{}, errors.New("message is undefined") + } + + return ValidatorData{ + Address: common.BytesToAddress(addrBytes), + Message: messageBytes, + }, nil +} - if raw.Types == nil { - return errors.New("types are undefined") +// 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 := raw.Types.IsValid(); err != nil { - return err + if err := _types.IsValid(); err != nil { + return TypedData{}, err } - typedData.Types = raw.Types - if raw.Types["EIP712Domain"] == nil { - return errors.New("domain types are undefined") + if _types["EIP712Domain"] == nil { + return TypedData{}, errors.New("domain types are undefined") } - if err := raw.Domain.IsValid(); err != nil { - return err + + domain, err := UnmarshalDomain(data) + if err != nil { + return TypedData{}, err } - typedData.Domain = raw.Domain - if len(raw.PrimaryType) == 0 { - return errors.New("primary type is undefined") + primaryType, ok := raw["primaryType"].(string) + if !ok || len(primaryType) == 0 { + return TypedData{}, errors.New("primary type is undefined") } - typedData.PrimaryType = raw.PrimaryType - if raw.Message == nil { - return errors.New("message is undefined") + message, ok := raw["message"].(EIP712Data) + if !ok || message == nil { + return TypedData{}, errors.New("message is undefined") } - typedData.Message = raw.Message + return TypedData{ + Types: _types, + PrimaryType: primaryType, + Domain: domain, + Message: message, + }, nil +} - return 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 func (typedData *TypedData) Map() map[string]interface{} { dataMap := map[string]interface{}{ - "Types": typedData.Types, - "Domain": typedData.Domain.Map(), - "PrimaryType": typedData.PrimaryType, - "Message": typedData.Message, + "types": typedData.Types, + "domain": typedData.Domain.Map(), + "primaryType": typedData.PrimaryType, + "message": typedData.Message, } return dataMap diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index f25699bb2d91..e1483b7afff9 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -21,6 +21,7 @@ import ( "fmt" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "math/big" "testing" ) @@ -77,10 +78,10 @@ var domainStandard = EIP712Domain{ "1", big.NewInt(1), "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", - nil, + "", } -var dataStandard = map[string]interface{}{ +var messageStandard = map[string]interface{}{ "from": map[string]interface{}{ "name": "Cow", "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", @@ -93,12 +94,19 @@ var dataStandard = map[string]interface{}{ } var typedData = TypedData{ - typesStandard, - primaryType, - domainStandard, - dataStandard, + Types: typesStandard, + PrimaryType: primaryType, + Domain: domainStandard, + Message: messageStandard, } +//var typedDataMap = map[string]interface{}{ +// "types": typesStandard, +// "primaryType": primaryType, +// "domain": domainStandard, +// "message": messageStandard, +//} + func TestSignData(t *testing.T) { api, control := setup(t) //Create two accounts @@ -113,7 +121,7 @@ func TestSignData(t *testing.T) { control <- "Y" control <- "wrongpassword" - signature, err := api.SignData(context.Background(), TextPlain.Mime, a, []byte("EHLO world")) + signature, err := api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world"))) if signature != nil { t.Errorf("Expected nil-data, got %x", signature) } @@ -121,7 +129,7 @@ func TestSignData(t *testing.T) { t.Errorf("Expected ErrLocked! %v", err) } control <- "No way" - signature, err = api.SignData(context.Background(), TextPlain.Mime, a, []byte("EHLO world")) + signature, err = api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world"))) if signature != nil { t.Errorf("Expected nil-data, got %x", signature) } @@ -131,7 +139,7 @@ func TestSignData(t *testing.T) { // text/plain control <- "Y" control <- "a_long_password" - signature, err = api.SignData(context.Background(), TextPlain.Mime, a, []byte("EHLO world")) + signature, err = api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world"))) if err != nil { t.Fatal(err) } @@ -141,7 +149,7 @@ func TestSignData(t *testing.T) { // data/typed control <- "Y" control <- "a_long_password" - signature, err = api.SignTypedData(context.Background(), a, typedData) + signature, err = api.SignData(context.Background(), DataTyped.Mime, a, typedData.Map()) if err != nil { t.Fatal(err) } From 19d9b2a7741cab221cd0a1c6c3b169716f950d2f Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Fri, 2 Nov 2018 12:03:14 +0100 Subject: [PATCH 53/84] Fixed bugs in 'data/typed' signs --- cmd/clef/audit.log | 2 + signer/core/api.go | 8 +- signer/core/auditlog.go | 20 ++-- signer/core/signed_data.go | 163 +++++++------------------------- signer/core/signed_data_test.go | 20 ++-- 5 files changed, 56 insertions(+), 157 deletions(-) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log index 0ce97ab5fe16..28aa2f35965f 100644 --- a/cmd/clef/audit.log +++ b/cmd/clef/audit.log @@ -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: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-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!]}" diff --git a/signer/core/api.go b/signer/core/api.go index 49039373725e..49532f6ac43b 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -48,10 +48,10 @@ type ExternalAPI interface { SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) // SignData - request to sign the given data (plus prefix) 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(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) + // SignTypedData - request to sign the given structured data (plus prefix) + SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) // 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(ctx context.Context, addr common.Address) (json.RawMessage, error) // Import - request to import an account @@ -175,7 +175,7 @@ type ( SignDataRequest struct { ContentType string `json:"content_type"` Address common.MixedcaseAddress `json:"address"` - Rawdata interface{} `json:"raw_data"` + Rawdata interface{} `json:"raw_data"` Message string `json:"message"` Hash hexutil.Bytes `json:"hash"` Meta Metadata `json:"meta"` diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go index b9769b16e91c..578e5ddcb54a 100644 --- a/signer/core/auditlog.go +++ b/signer/core/auditlog.go @@ -70,18 +70,18 @@ func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr com return b, e } -//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(), -// "addr", addr.String(), "data", data) -// b, e := l.api.SignTypedData(ctx, addr, data) -// l.log.Info("SignTypedData", "type", "response", "data", common.Bytes2Hex(b), "error", e) -// return b, e -//} +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(), + "addr", addr.String(), "data", data) + b, e := l.api.SignTypedData(ctx, addr, data) + l.log.Info("SignTypedData", "type", "response", "data", common.Bytes2Hex(b), "error", 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(), - "data", common.Bytes2Hex(data), "sig", common.Bytes2Hex(sig), "content-type", contentType) - b, e := l.api.EcRecover(ctx, contentType, data, sig) + "data", common.Bytes2Hex(data), "sig", common.Bytes2Hex(sig)) + b, e := l.api.EcRecover(ctx, data, sig) l.log.Info("EcRecover", "type", "response", "address", b.String(), "error", e) return b, e } diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index f86845ed2f28..b8dd636a87d2 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -19,6 +19,7 @@ package core import ( "bytes" "context" + "encoding/json" "errors" "fmt" "math/big" @@ -63,8 +64,8 @@ var ( ) type ValidatorData struct { - Address common.Address - Message hexutil.Bytes + Address common.Address + Message hexutil.Bytes } type TypedData struct { @@ -86,11 +87,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 string `json:"verifyingContract"` - Salt string `json:"salt"` + Name string `json:"name"` + Version string `json:"version"` + ChainId *big.Int `json:"chainId"` + VerifyingContract string `json:"verifyingContract"` + Salt string `json:"salt"` } const ( @@ -99,7 +100,7 @@ const ( TypeBytes = "bytes" TypeInt = "int" TypeString = "string" - TypeUint = "uint" + TypeUint = "uint" ) // Sign receives a request and produces a signature @@ -139,7 +140,7 @@ func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, re // // Different types of validation occur. func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error) { - var req, err= api.determineSignatureFormat(contentType, addr, data) + var req, err = api.determineSignatureFormat(contentType, addr, data) if err != nil { return nil, err } @@ -173,19 +174,8 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M return nil, err } sighash, msg := SignTextValidator(validatorData) - fmt.Printf("sighash:%s", sighash) req = &SignDataRequest{Rawdata: validatorData, Message: msg, Hash: sighash, ContentType: mediaType} 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: // Clique is the Ethereum PoA standard 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 // with the given validator. -// // hash = keccak256("\x19\x00"${address}${data}). func SignTextValidator(validatorData ValidatorData) (hexutil.Bytes, string) { msg := fmt.Sprintf("\x19\x00%s%s", string(validatorData.Address.Bytes()), string(validatorData.Message)) @@ -267,59 +256,41 @@ func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { // 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 // signing of transactions. -// // hash = keccak256("\x19$Ethereum Signed Message:\n"${message length}${message}). func SignTextPlain(data hexutil.Bytes) (hexutil.Bytes, string) { // The letter `E` is \x45 in hex, retrofitting // 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 } -// SignDataTyped signs EIP-712 conformant typed data +// SignTypedData signs EIP-712 conformant typed data // hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}") -func SignDataTyped(typedData TypedData) (hexutil.Bytes, string) { +func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, typedData TypedData) (hexutil.Bytes, error) { 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 + _, err := json.Marshal(typedData.Map()) + if err != nil { + return nil, err + } + msg := fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)) + sighash := crypto.Keccak256([]byte(msg)) + req := &SignDataRequest{Rawdata: typedData.Map(), Message: msg, Hash: sighash, ContentType: DataTyped.Mime} + signature, err := api.Sign(ctx, addr, req) + if err != nil { + api.UI.ShowError(err.Error()) + return nil, err + } + return signature, nil } -// 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) { -// 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 -// } -// buffer := bytes.Buffer{} -// buffer.WriteString("\x19") -// 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()) -// return nil, err -// } -// return signature, nil -//} - // HashStruct generates the following encoding for the given domain and message: // `encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01" β€– domainSeparator β€– hashStruct(message)` func (typedData *TypedData) HashStruct(primaryType string, data EIP712Data) hexutil.Bytes { 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 { includes := func(arr []string, str string) bool { for _, obj := range arr { @@ -347,7 +318,7 @@ func (typedData *TypedData) Dependencies(primaryType string, found []string) []s return found } -// encodeType generates the following encoding: +// EncodeType generates the following encoding: // `name β€– "(" β€– member₁ β€– "," β€– memberβ‚‚ β€– "," β€– … β€– memberβ‚™ ")"` // // 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{}): return _interface.([]uint8) case reflect.TypeOf(string("")): - return common.Hex2Bytes(_interface.(string)) + return hexutil.Bytes(_interface.(string)) default: break } @@ -507,7 +478,7 @@ func bytesValueOf(_interface interface{}) hexutil.Bytes { // EcRecover recovers the address associated with the given sig. // 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. // // 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 } -// 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 func (typedData *TypedData) Map() map[string]interface{} { dataMap := map[string]interface{}{ @@ -686,7 +589,7 @@ func isStandardTypeStr(encType string) bool { } // Dynamic types - for _, standardType := range []string { + for _, standardType := range []string{ TypeBytes, TypeInt, TypeUint, @@ -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 { - return errors.New("domain undefined") + return errors.New("domain is undefined") } return nil @@ -740,4 +643,4 @@ func (domain *EIP712Domain) Map() map[string]interface{} { dataMap["salt"] = domain.Salt } return dataMap -} \ No newline at end of file +} diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index e1483b7afff9..abb5a7ac73c6 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -19,11 +19,12 @@ package core import ( "context" "fmt" + "math/big" + "testing" + "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "math/big" - "testing" ) var typesStandard = EIP712Types{ @@ -94,19 +95,12 @@ var messageStandard = map[string]interface{}{ } var typedData = TypedData{ - Types: typesStandard, + Types: typesStandard, PrimaryType: primaryType, - Domain: domainStandard, - Message: messageStandard, + Domain: domainStandard, + Message: messageStandard, } -//var typedDataMap = map[string]interface{}{ -// "types": typesStandard, -// "primaryType": primaryType, -// "domain": domainStandard, -// "message": messageStandard, -//} - func TestSignData(t *testing.T) { api, control := setup(t) //Create two accounts @@ -149,7 +143,7 @@ func TestSignData(t *testing.T) { // data/typed control <- "Y" 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 { t.Fatal(err) } From 90241ef49e892a8e601616cfc3055799c67b2099 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Fri, 2 Nov 2018 12:47:14 +0100 Subject: [PATCH 54/84] Brought legal warning back after temporarily disabling it for development --- cmd/clef/main.go | 26 +++---- signer/core/api_layout.md | 150 -------------------------------------- 2 files changed, 11 insertions(+), 165 deletions(-) delete mode 100644 signer/core/api_layout.md diff --git a/cmd/clef/main.go b/cmd/clef/main.go index be192090c983..f3464b2ff6a3 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -26,8 +26,6 @@ import ( "encoding/hex" "encoding/json" "fmt" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/rlp" "io" "io/ioutil" "math/big" @@ -332,10 +330,9 @@ func initialize(c *cli.Context) error { // If using the stdioui, we can't do the 'confirm'-flow fmt.Fprintf(logOutput, legalWarning) } else { - // Temporarily disabled - //if !confirm(legalWarning) { - // return fmt.Errorf("aborted by user") - //} + if !confirm(legalWarning) { + return fmt.Errorf("aborted by user") + } } log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int(logLevelFlag.Name)), log.StreamHandler(logOutput, log.TerminalFormat(true)))) @@ -558,15 +555,14 @@ 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 - //resp, err := ui.OnInputRequired(core.UserInputRequest{ - // Title: "Master Password", - // Prompt: "Please enter the password to decrypt the master seed", - // IsPassword: true}) - //if err != nil { - // return nil, err - //} - //password = resp.Text + resp, err := ui.OnInputRequired(core.UserInputRequest{ + Title: "Master Password", + Prompt: "Please enter the password to decrypt the master seed", + IsPassword: true}) + if err != nil { + return nil, err + } + password = resp.Text } else { password = getPassPhrase("Decrypt master seed of clef", false) } diff --git a/signer/core/api_layout.md b/signer/core/api_layout.md deleted file mode 100644 index 3aae11fd07b7..000000000000 --- a/signer/core/api_layout.md +++ /dev/null @@ -1,150 +0,0 @@ -# Specs -`encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01EthereumSignedMessage\n" β€– domainSeparator β€– hashStruct(message)` -- data adheres to π•Š, a structure defined in the rigorous eip-712 -- `\x01` is needed to comply with EIP-191 -- `domainSeparator` and `hashStruct` are defined below - -## A) domainSeparator -`domainSeparator = hashStruct(eip712Domain)` -
-
-Struct named `EIP712Domain` with one or more of the below fields: - -- `string name` -- `string version` -- `uint256 chainId`, as per EIP-155 -- `address verifyingContract` -- `bytes32 salt` - -## B) hashStruct -`hashStruct(s : π•Š) = keccak256(typeHash β€– encodeData(s))` -
-`typeHash = keccak256(encodeType(typeOf(s)))` - -### i) encodeType -- `name β€– "(" β€– member₁ β€– "," β€– memberβ‚‚ β€– "," β€– … β€– memberβ‚™ ")"` -- each member is written as `type β€– " " β€– name` -- encodings cascade down and are sorted by name - -Example: `Mail(Person from,Person to,string contents)Person(string name,address wallet)` - -### ii) encodeData -- `enc(value₁) β€– enc(valueβ‚‚) β€– … β€– enc(valueβ‚™)` -- each encoded member is 32-byte long - - #### a) atomic - - - `bool` => `uint256` - - `address` => `uint160` - - `int8:int256` and `uint8:uint256` => sign-extended `uint256` in big endian order - - `bytes1:31` => `bytes32` - - #### b) dynamic - - - `bytes` => `keccak256(bytes)` - - `string` => `keccak256(string)` - - #### c) referenced - - - `array` => `keccak256(encodeData(array))` - - `struct` => `rec(keccak256(hashStruct(struct)))` - -## C) Algo -- hashStruct - - encodeType - - encodeData - - if primitive - - encode - - else - - if array - - encodeData - - else if struct - - hashStruct - - else - - break - -## D) Example -### Query -```json -{ - "jsonrpc": "2.0", - "method": "account_signTypedData", - "params": [ - "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", - { - "types": { - "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" - } - ] - }, - "primaryType": "Mail", - "domain": { - "name": "Ether Mail", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "from": { - "name": "Cow", - "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to": { - "name": "Bob", - "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents": "Hello, Bob!" - } - } - ], - "id": 1 -} -``` - -### Response -```json -{ - "id":1, - "jsonrpc": "2.0", - "result": "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c" -} -``` From 65878a0b8432795a70ce65f0d333148bb329b91f Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Mon, 5 Nov 2018 17:54:58 +0200 Subject: [PATCH 55/84] Added example RPC calls for account_signData and account_signTypedData --- cmd/clef/README.md | 8 ++++---- cmd/clef/extapi_changelog.md | 2 +- signer/core/signed_data.go | 17 ++--------------- 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/cmd/clef/README.md b/cmd/clef/README.md index be0e10747e89..5e6f8661c798 100644 --- a/cmd/clef/README.md +++ b/cmd/clef/README.md @@ -350,15 +350,13 @@ Bash example: {"jsonrpc":"2.0","id":67,"result":{"raw":"0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","tx":{"nonce":"0x0","gasPrice":"0x1","gas":"0x333","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0","value":"0x0","input":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012","v":"0x26","r":"0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e","s":"0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","hash":"0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e"}}} ``` - ### account_signData #### Sign data Signs a chunk of data and returns the calculated signature. #### Arguments - - - content type [string]: type of data to sign + - content type [string]: type of signed data - `text/validator`: hex data with custom validator defined in a contract - `application/clique`: [clique](https://github.com/ethereum/EIPs/issues/225) headers - `text/plain`: simple hex data validated by `account_ecRecover` @@ -491,11 +489,13 @@ Response ### account_ecRecover #### Sign data +<<<<<<< HEAD +======= +>>>>>>> c72099670... Added example RPC calls for account_signData and account_signTypedData Derive the address from the account that was used to sign data with content type `text/plain` and the signature. #### Arguments - - content type [string]: type of signed data - data [data]: data that was signed - signature [data]: the signature to verify diff --git a/cmd/clef/extapi_changelog.md b/cmd/clef/extapi_changelog.md index a9ab42e5b521..7adc33960590 100644 --- a/cmd/clef/extapi_changelog.md +++ b/cmd/clef/extapi_changelog.md @@ -8,7 +8,7 @@ The addition of `contentType` makes it possible to use the method for different * signing data with an intended validator (not yet implemented) * signing clique headers, * signing plain personal messages, -* The external method `account_signTypedData` [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md) and makes it possible to sign typed data. +* The external method `account_signTypedData` implements [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md) and makes it possible to sign typed data. #### 4.0.0 diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index b8dd636a87d2..532d2c99a5c2 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -175,7 +175,6 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M } sighash, msg := SignTextValidator(validatorData) req = &SignDataRequest{Rawdata: validatorData, Message: msg, Hash: sighash, ContentType: mediaType} - break case ApplicationClique.Mime: // Clique is the Ethereum PoA standard cliqueData, err := hexutil.Decode(data.(string)) @@ -192,7 +191,6 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M } msg := fmt.Sprintf("clique block %d [0x%x]", header.Number, header.Hash()) req = &SignDataRequest{Rawdata: cliqueData, Message: msg, Hash: sighash, ContentType: mediaType} - break case TextPlain.Mime: // Calculates an Ethereum ECDSA signature for: // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") @@ -202,7 +200,6 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M } sighash, msg := SignTextPlain(plainData) req = &SignDataRequest{Rawdata: plainData, Message: msg, Hash: sighash, ContentType: mediaType} - break default: return nil, fmt.Errorf("content type '%s' not implemented for signing", contentType) } @@ -382,7 +379,6 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter bytesValue = append(bytesValue, _byte) } primitiveEncValue = bytesValue - break case "bool": primitiveEncType = "uint256" var int64Val int64 @@ -390,11 +386,9 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter 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") @@ -404,15 +398,12 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter for i := 0; i < 32-size; i++ { bytesValue = append(bytesValue, 0) } - for _, _byte := range encValue.(hexutil.Bytes) { - bytesValue = append(bytesValue, _byte) - } + bytesValue = append(bytesValue, encValue.(hexutil.Bytes)...) primitiveEncValue = bytesValue } else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { primitiveEncType = "uint256" primitiveEncValue = abi.U256(encValue.(*big.Int)) } - break } return primitiveEncType, primitiveEncValue } @@ -600,11 +591,7 @@ func isStandardTypeStr(encType string) bool { } // Reference types - if encType[len(encType)-1] == ']' { - return true - } - - return false + return encType[len(encType)-1] == ']' } // IsValid checks if the given domain is valid, i.e. contains at least From f4f4877c3bba3f445c9df112c7cf501891761c23 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Thu, 8 Nov 2018 23:08:41 +0200 Subject: [PATCH 56/84] Polished and fixed PR --- accounts/accounts.go | 2 +- cmd/clef/audit.log | 406 ------------------------------------- signer/core/signed_data.go | 11 +- 3 files changed, 6 insertions(+), 413 deletions(-) delete mode 100644 cmd/clef/audit.log diff --git a/accounts/accounts.go b/accounts/accounts.go index a0675ab7a609..cb1eae281587 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -109,7 +109,7 @@ type Wallet interface { // a password to decrypt the account, or a PIN code to verify the transaction), // an AuthNeededError instance will be returned, containing infos for the user // about which fields or actions are needed. The user may retry by providing - // the needed details via SignHashWithPassphraseSignTxWithPassphrase, or by other means (e.g. unlock + // the needed details via SignTxWithPassphrase, or by other means (e.g. unlock // the account in a keystore). SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log deleted file mode 100644 index 28aa2f35965f..000000000000 --- a/cmd/clef/audit.log +++ /dev/null @@ -1,406 +0,0 @@ -t=2018-10-13T05:03:16-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-13T05:04:21-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-13T05:04:35-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61564\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr=data LOG15_ERROR= LOG15_ERROR="Normalized odd number of arguments by adding nil" -t=2018-10-13T05:04:35-0700 lvl=info msg=SignStructuredData api=signer type=response data= error=nil -t=2018-10-13T05:07:31-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-13T05:35:59-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-13T05:37:45-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-13T05:42:26-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-13T05:43:13-0700 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61669\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[type:string name:version] 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:{neg:false abs:[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-13T05:43:13-0700 lvl=info msg=SignTypedData api=signer type=response data= error=nil -t=2018-10-13T05:43:50-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-13T05:44:06-0700 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61680\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Mail:[map[name:from type:Person] map[type:Person name:to] 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]] Person:[map[type:string name:name] map[name:wallet type:address]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:{neg:false abs:[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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" -t=2018-10-13T05:44:06-0700 lvl=info msg=SignTypedData api=signer type=response data= error=nil -t=2018-10-13T05:46:53-0700 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-13T05:46:58-0700 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61694\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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-13T05:46:58-0700 lvl=info msg=SignTypedData api=signer type=response data= error=nil -t=2018-10-14T21:00:04+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T21:00:06+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56799\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-14T21:00:06+0100 lvl=info msg=SignTypedData api=signer type=response data= error="Couldn't decode types of EIP712Domain" -t=2018-10-14T21:00:50+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T21:01:02+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56810\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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:{neg:false abs:[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-14T21:01:02+0100 lvl=info msg=SignTypedData api=signer type=response data= error="Couldn't decode types of EIP712Domain" -t=2018-10-14T21:01:39+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T21:01:53+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56823\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" -t=2018-10-14T21:01:53+0100 lvl=info msg=SignTypedData api=signer type=response data= error="Couldn't decode types of EIP712Domain" -t=2018-10-14T21:11:30+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T21:11:33+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56899\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] map[name:verifyingContract type:address]] Person:[map[type:string name:name] 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:{neg:false abs:[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-14T21:11:33+0100 lvl=info msg=SignTypedData api=signer type=response data= error="Couldn't decode types of EIP712Domain" -t=2018-10-14T21:15:28+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T21:15:34+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56968\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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-14T21:15:34+0100 lvl=info msg=SignTypedData api=signer type=response data= error="Couldn't decode types of EIP712Domain" -t=2018-10-14T21:17:53+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T21:17:57+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56991\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:Person name:to] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:{neg:false abs:[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-14T21:17:57+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-14T21:18:32+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T21:18:36+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57006\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:Person name:from] map[name:to type:Person] map[type:string name:contents]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:{neg:false abs:[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-14T21:18:36+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-14T21:18:59+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T21:19:00+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57017\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] EIP712Domain:[map[name:name type:string] map[type:string name:version] map[name:chainId type:uint256] map[name:verifyingContract type:address]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:{neg:false abs:[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-14T21:19:00+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-14T21:30:19+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T21:30:29+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57188\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:{neg:false abs:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-14T21:30:29+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-14T21:51:40+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T21:51:42+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57392\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] EIP712Domain:[map[name:name type:string] map[type:string name:version] 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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-14T21:51:42+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-14T22:16:07+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T22:16:14+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57602\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]]}" -t=2018-10-14T22:16:14+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-14T22:18:31+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T22:18:35+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57653\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" -t=2018-10-14T22:18:35+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-14T22:20:47+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-14T22:20:50+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57669\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]] EIP712Domain:[map[name:name type:string] map[type:string name:version] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet 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-14T22:20:50+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-15T03:11:09+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-15T03:11:17+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50605\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-15T03:11:17+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-15T03:12:01+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-15T03:12:04+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50618\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" -t=2018-10-15T03:12:04+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-15T03:14:48+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-15T03:15:25+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50635\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-15T03:15:25+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil -t=2018-10-20T15:17:32+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:17:53+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:22:24+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:24:48+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:24:54+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56168\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[] PrimaryType: Domain:{Name: Version: ChainId: VerifyingContract:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Salt:0x} Message:map[]}" -t=2018-10-20T15:24:54+0100 lvl=info msg=SignTypedData api=signer type=response data= error="primary type undefined" -t=2018-10-20T15:25:28+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:27:12+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:27:59+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:28:02+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56262\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[] PrimaryType: Domain:{Name: Version: ChainId: VerifyingContract:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Salt:0x} Message:map[]}" -t=2018-10-20T15:28:02+0100 lvl=info msg=SignTypedData api=signer type=response data= error="primary type undefined" -t=2018-10-20T15:28:15+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56262\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[] PrimaryType: Domain:{Name: Version: ChainId: VerifyingContract:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Salt:0x} Message:map[]}" -t=2018-10-20T15:28:15+0100 lvl=info msg=SignTypedData api=signer type=response data= error="primary type undefined" -t=2018-10-20T15:29:47+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:29:52+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56281\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[] PrimaryType: Domain:{Name: Version: ChainId: VerifyingContract:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Salt:0x} Message:map[]}" -t=2018-10-20T15:32:20+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:32:46+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56456\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:Person name:from] map[type:Person name:to] 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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-20T15:34:35+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:35:24+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56476\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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:[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-20T15:37:05+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:37:21+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56561\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:Person name:to] 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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" -t=2018-10-20T15:38:27+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:38:36+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56583\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[type:string name:version] 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[to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" -t=2018-10-20T15:40:49+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:40:54+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56604\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T15:41:20+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:41:38+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56681\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[type:string name:name] map[type:string name:version] 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:[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-20T15:49:10+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:49:20+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56804\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:string name:contents]]] 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-20T15:51:01+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:51:31+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56859\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Mail:[map[type:Person name:from] 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]] Person:[map[name:name type:string] map[name:wallet 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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-20T15:51:59+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T15:52:23+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56905\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T16:05:44+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:05:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58137\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-20T16:06:22+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:06:25+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58154\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[type:string name:name] map[type:string name:version] 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[type:Person name:to] 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-20T16:06:39+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:06:43+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58213\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[type:string name:name] map[name:version type:string] map[name:chainId type:uint256] map[type:address name:verifyingContract]] 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:[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-20T16:06:55+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:07:53+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58301\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] Person:[map[type:string name:name] map[name:wallet 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[contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]]}" -t=2018-10-20T16:10:03+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:10:05+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58446\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:string name:contents]] 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:[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-20T16:10:39+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:10:43+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58497\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T16:12:31+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:12:37+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58538\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[type:string name:version] 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:[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-20T16:12:55+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58538\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" -t=2018-10-20T16:14:08+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:14:11+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58563\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[type:string name:version] map[name:chainId type:uint256] map[type:address name:verifyingContract]] 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:[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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice]]}" -t=2018-10-20T16:17:07+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:17:13+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58619\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T16:18:51+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:19:01+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:20:03+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58718\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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:[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-20T16:24:34+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:25:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58919\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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-20T16:30:18+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:30:40+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58983\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T16:30:59+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:31:01+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59036\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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-20T16:31:36+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T16:32:25+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59121\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice]]}" -t=2018-10-20T19:01:23+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T19:01:51+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59614\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] Person:[map[name:name type:string] map[name:wallet 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-20T19:02:26+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T19:02:41+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59722\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T19:06:00+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T19:07:47+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60455\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T19:08:16+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60455\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] 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[contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]]}" -t=2018-10-20T19:09:46+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60587\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T19:11:12+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T19:11:21+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60710\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" -t=2018-10-20T19:11:35+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60710\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] 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-20T19:11:51+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T19:11:53+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60768\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] 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-20T19:12:11+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T19:12:30+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60809\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" -t=2018-10-20T19:13:53+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T19:14:04+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60898\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-20T19:14:32+0100 lvl=info msg=SignTypedData api=signer type=response data=686365d6fa3d2a98d7c67101a8acf295c644a8de80b8ecc89cff276355897f6d error=nil -t=2018-10-20T19:36:35+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-20T19:36:40+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61953\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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-20T19:36:40+0100 lvl=info msg=SignTypedData api=signer type=response data=7951623617a41fec181fafc3555c0a494d01dce0408b6596964f5f50acba239e error=nil -t=2018-10-21T14:58:59+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T15:00:47+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T15:00:47+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52242\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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-21T15:05:18+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T15:07:18+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T15:07:20+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52313\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[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]] Person:[map[name:name type:string] map[type:address name:wallet]]] 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-21T15:07:45+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T15:07:48+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52326\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-21T15:08:15+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T15:08:18+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52339\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:uint256 name:chainId] 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-21T15:08:46+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T15:08:51+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52351\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" -t=2018-10-21T15:09:58+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52360\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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:[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-21T15:10:10+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T15:10:23+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52371\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" 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[type:string name:contents]]] 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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-21T15:11:27+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52373\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=deadbeef content-type=text/plain -t=2018-10-21T15:47:19+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T15:54:17+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:53039\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"Apache-HttpClient/4.5.5 (Java/1.8.0_152-release)\",\"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:[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-21T18:53:59+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T18:54:53+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T18:54:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54450\",\"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:[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-21T19:00:29+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:00:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54496\",\"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[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:[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-21T19:01:35+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:01:45+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54509\",\"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[type:Person name:from] map[type:Person name:to] map[type:string name:contents]] 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-21T19:06:53+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:07:33+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:07:56+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54595\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66D76e6A80DC7D46d7EC1b79b15Dfa34c3C6eF21 [chksum ok]" data="{Types:map[Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]] EIP712Domain:[map[name:name type:string] map[type:string name:version] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet 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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-21T19:09:56+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:09:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54620\",\"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[type:address name:verifyingContract]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[type:Person name:to] map[type:string name:contents]]] 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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-21T19:10:23+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:10:46+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54635\",\"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:[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-21T19:16:04+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:16:11+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54666\",\"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[type:uint256 name:chainId] 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[from:map[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" -t=2018-10-21T19:19:07+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:19:12+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54690\",\"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[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" -t=2018-10-21T19:20:49+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:20:54+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54713\",\"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:[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-21T19:53:52+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:54:07+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54849\",\"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:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" -t=2018-10-21T19:55:14+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:55:19+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54863\",\"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[type:string name:version] map[name:chainId type:uint256] map[type:address name:verifyingContract]] 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:[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-21T19:56:13+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:56:18+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54874\",\"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[type:uint256 name:chainId] map[type:address name:verifyingContract]] 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:[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[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice] to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-21T19:57:02+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:57:41+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:57:43+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54896\",\"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[type:string name:version] 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:[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[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" -t=2018-10-21T19:58:03+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:58:04+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54908\",\"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[type:uint256 name:chainId] map[type:address name:verifyingContract]] 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:[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-21T19:58:39+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T19:58:44+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54920\",\"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:[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-21T20:00:09+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-21T20:00:34+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:54933\",\"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[type:address name:verifyingContract]] 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:[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:03:51+0100 lvl=info msg=Configured api=signer audit log=audit.log -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!]}" -t=2018-10-25T13:47:17+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T13:47:54+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T13:50:09+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50039\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T13:51:00+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T13:51:07+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:51792\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T13:57:27+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T13:57:30+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:62734\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T14:01:29+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T14:01:30+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:53871\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608adeadbeef content-type=text/validator -t=2018-10-25T14:02:00+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T14:02:10+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:55131\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608adeadbeef content-type=text/validator -t=2018-10-25T14:13:15+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T14:13:16+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58361\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T14:13:53+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T14:14:04+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59928\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T14:14:39+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T14:14:43+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61106\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T14:21:56+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T14:22:59+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59342\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=60deadbeef content-type=text/validator -t=2018-10-25T14:22:59+0200 lvl=info msg=SignData api=signer type=response data= error="validator address and data undefined" -t=2018-10-25T14:23:05+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59342\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T14:23:31+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T14:23:55+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61180\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T20:12:47+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T20:13:00+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59607\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T20:34:51+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T20:34:53+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:62187\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T20:35:38+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T20:35:43+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:62300\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-25T20:41:50+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-25T20:42:06+0200 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:63054\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-26T10:33:40+0200 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T11:29:41+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T11:30:16+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T11:30:28+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:64014\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-31T11:39:42+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T11:48:29+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T11:48:37+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:64737\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-31T11:50:49+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T11:50:59+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:64820\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-31T11:51:44+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T11:52:05+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:64849\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=608af3436e40964b9f2c7214953a3f03685f62b8deadbeef content-type=text/validator -t=2018-10-31T14:46:14+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T14:47:22+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:51211\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe validator:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator -t=2018-10-31T14:47:22+0100 lvl=info msg=SignData api=signer type=response data= error=nil -t=2018-10-31T15:45:57+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T15:46:31+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:51638\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[validator:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T15:46:31+0100 lvl=info msg=SignData api=signer type=response data= error="validator data not constructed correctly" -t=2018-10-31T15:46:57+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T15:47:00+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:51654\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[validator:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T15:47:00+0100 lvl=info msg=SignData api=signer type=response data= error="validator data not constructed correctly" -t=2018-10-31T16:24:12+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:24:30+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:29:29+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:51956\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[validator:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T16:35:14+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:35:19+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52038\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T16:35:19+0100 lvl=info msg=SignData api=signer type=response data= error="message is undefined" -t=2018-10-31T16:35:41+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:36:12+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52052\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T16:37:15+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:37:18+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52065\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator -t=2018-10-31T16:38:10+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:38:13+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52080\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0x1c]" content-type=text/validator -t=2018-10-31T16:38:39+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:38:42+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52097\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0x1c]" content-type=text/validator -t=2018-10-31T16:38:57+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:39:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52109\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator -t=2018-10-31T16:47:12+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:47:22+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52263\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T16:50:27+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:50:52+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:51:19+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:52:19+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T16:53:07+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52394\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T17:01:45+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T17:01:56+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T17:02:14+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52600\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T17:03:31+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T17:03:35+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52624\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T17:04:19+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T17:04:24+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52643\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator -t=2018-10-31T17:05:01+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T17:05:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52657\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T17:11:09+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T17:11:13+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52695\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T17:12:22+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T17:12:38+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T17:12:38+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:52724\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T19:15:08+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:16:59+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:49906\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T19:28:45+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:29:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50208\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T19:29:21+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:29:32+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50219\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T19:35:37+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:35:38+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50288\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T19:36:22+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:36:29+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50310\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator -t=2018-10-31T19:36:56+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:36:59+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50328\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator -t=2018-10-31T19:40:24+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:40:26+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50369\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator -t=2018-10-31T19:41:50+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:43:03+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50469\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T19:43:58+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:44:14+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50485\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xdeadbeef]" content-type=text/validator -t=2018-10-31T19:44:30+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:44:32+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50501\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T19:47:37+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:47:56+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50552\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-10-31T19:48:10+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-10-31T19:48:39+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50567\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[address:0x0DDFb8Fe404fE28179580879A918c4e798866c08 message:0xcafebabe]" content-type=text/validator -t=2018-11-01T19:16:25+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T19:16:31+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:57757\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0x0DDFb8Fe404fE28179580879A918c4e798866c08]" content-type=text/validator -t=2018-11-01T21:11:47+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:13:52+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59254\",\"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-01T21:14:40+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:14:48+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59317\",\"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-01T21:16:00+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:16:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59339\",\"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-01T21:18:49+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:18:50+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59375\",\"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-01T21:26:42+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:26:55+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59485\",\"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-01T21:34:20+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:34:24+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59590\",\"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-01T21:37:05+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:37:48+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59652\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="map[message:0xcafebabe address:0xd090B1504cC9341BC18cE5d34D750545bb515FdC]" content-type=text/validator -t=2018-11-01T21:43:00+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:43:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59721\",\"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-01T21:44:59+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:45:01+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59759\",\"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-01T21:45:19+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:45:21+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59771\",\"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-01T21:46:31+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:46:33+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59786\",\"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-01T21:48:12+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:48:13+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59830\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain -t=2018-11-01T21:48:47+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:49:03+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:49:03+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59851\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain -t=2018-11-01T21:50:58+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:51:02+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59872\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain -t=2018-11-01T21:52:07+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:52:09+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59892\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain -t=2018-11-01T21:52:21+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:52:22+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59904\",\"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-01T21:53:12+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:53:14+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59919\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain -t=2018-11-01T21:53:51+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:54:08+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59957\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain -t=2018-11-01T21:54:21+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T21:54:26+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59971\",\"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-01T21:59:18+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:00:23+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60071\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain -t=2018-11-01T22:00:57+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:01:04+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60083\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain -t=2018-11-01T22:06:16+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:06:20+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60136\",\"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:07:41+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:07:42+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60170\",\"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:08:13+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:09:25+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60229\",\"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:11:52+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:11:53+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60257\",\"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:13:09+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:13:33+0100 lvl=info msg=EcRecover api=signer type=request metadata="{\"remote\":\"127.0.0.1:60277\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" data=cafebabe sig=d7cabcefb177b419f8c4d124cdf3487dfdf2f8e4cceb9bad5dfb8707130c9b7b394e0e7057335ce459145ff6d6b5bd52242c54e11ae5934637358930f2f6d0651b content-type=text/plain -t=2018-11-01T22:13:33+0100 lvl=info msg=EcRecover api=signer type=response address=0x66D76e6A80DC7D46d7EC1b79b15Dfa34c3C6eF21 error=nil -t=2018-11-01T22:13:44+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60277\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data=0xcafebabe content-type=text/plain -t=2018-11-01T22:15:03+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:15:24+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60301\",\"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:15:36+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:15:39+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60319\",\"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:05+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:23:20+0100 lvl=info msg=Configured api=signer audit log=audit.log -t=2018-11-01T22:23:27+0100 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60494\",\"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:54+0100 lvl=info msg=Configured api=signer audit log=audit.log -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: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!]}" diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 532d2c99a5c2..5a736481cf5e 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -174,7 +174,7 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M return nil, err } sighash, msg := SignTextValidator(validatorData) - req = &SignDataRequest{Rawdata: validatorData, Message: msg, Hash: sighash, ContentType: mediaType} + req = &SignDataRequest{ContentType: mediaType, Rawdata: validatorData, Message: msg, Hash: sighash} case ApplicationClique.Mime: // Clique is the Ethereum PoA standard cliqueData, err := hexutil.Decode(data.(string)) @@ -190,7 +190,7 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M return nil, err } msg := fmt.Sprintf("clique block %d [0x%x]", header.Number, header.Hash()) - req = &SignDataRequest{Rawdata: cliqueData, Message: msg, Hash: sighash, ContentType: mediaType} + req = &SignDataRequest{ContentType: mediaType, Rawdata: cliqueData, Message: msg, Hash: sighash} case TextPlain.Mime: // Calculates an Ethereum ECDSA signature for: // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") @@ -199,7 +199,7 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M return nil, err } sighash, msg := SignTextPlain(plainData) - req = &SignDataRequest{Rawdata: plainData, Message: msg, Hash: sighash, ContentType: mediaType} + req = &SignDataRequest{ContentType: mediaType, Rawdata: plainData, Message: msg, Hash: sighash} default: return nil, fmt.Errorf("content type '%s' not implemented for signing", contentType) } @@ -272,7 +272,7 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd } msg := fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)) sighash := crypto.Keccak256([]byte(msg)) - req := &SignDataRequest{Rawdata: typedData.Map(), Message: msg, Hash: sighash, ContentType: DataTyped.Mime} + req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: typedData.Map(), Message: msg, Hash: sighash} signature, err := api.Sign(ctx, addr, req) if err != nil { api.UI.ShowError(err.Error()) @@ -281,8 +281,7 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd return signature, nil } -// HashStruct generates the following encoding for the given domain and message: -// `encode(domainSeparator : 𝔹²⁡⁢, message : π•Š) = "\x19\x01" β€– domainSeparator β€– hashStruct(message)` +// HashStruct generates a keccak256 hash of the encoding of the provided data func (typedData *TypedData) HashStruct(primaryType string, data EIP712Data) hexutil.Bytes { return crypto.Keccak256(typedData.EncodeData(primaryType, data)) } From bf752cf6e24819ed075487d9cf3ca998a9448fc1 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Fri, 9 Nov 2018 20:35:05 +0200 Subject: [PATCH 57/84] Solved malformed data panics and also wrote tests --- signer/core/cliui.go | 2 +- signer/core/signed_data.go | 141 ++++++++++++----- signer/core/signed_data_test.go | 267 +++++++++++++++++++++++++++++++- 3 files changed, 369 insertions(+), 41 deletions(-) diff --git a/signer/core/cliui.go b/signer/core/cliui.go index 035d4f201680..1e5927b242b0 100644 --- a/signer/core/cliui.go +++ b/signer/core/cliui.go @@ -164,7 +164,7 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResp fmt.Printf("-------- Sign data request--------------\n") fmt.Printf("Account: %s\n", request.Address.String()) - fmt.Printf("message: \n%v\n", request.Message) + fmt.Printf("message: \n%q\n", request.Message) fmt.Printf("raw data: \n%v\n", request.Rawdata) fmt.Printf("message hash: %v\n", request.Hash) fmt.Printf("-------------------------------------------\n") diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 5a736481cf5e..95454acfdc23 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -19,7 +19,6 @@ package core import ( "bytes" "context" - "encoding/json" "errors" "fmt" "math/big" @@ -264,9 +263,14 @@ func SignTextPlain(data hexutil.Bytes) (hexutil.Bytes, string) { // 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) { - domainSeparator := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) - typedDataHash := typedData.HashStruct(typedData.PrimaryType, typedData.Message) - _, err := json.Marshal(typedData.Map()) + if err := typedData.IsValid(); err != nil { + return nil, err + } + domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) + if err != nil { + return nil, err + } + typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) if err != nil { return nil, err } @@ -282,8 +286,12 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd } // HashStruct generates a keccak256 hash of the encoding of the provided data -func (typedData *TypedData) HashStruct(primaryType string, data EIP712Data) hexutil.Bytes { - return crypto.Keccak256(typedData.EncodeData(primaryType, data)) +func (typedData *TypedData) HashStruct(primaryType string, data EIP712Data) (hexutil.Bytes, error) { + encodedData, err := typedData.EncodeData(primaryType, data) + if err != nil { + return nil, err + } + return crypto.Keccak256(encodedData), nil } // Dependencies returns an array of custom types ordered by their hierarchical reference tree @@ -354,7 +362,7 @@ func (typedData *TypedData) TypeHash(primaryType string) hexutil.Bytes { // `enc(value₁) β€– enc(valueβ‚‚) β€– … β€– enc(valueβ‚™)` // // each encoded member is 32-byte long -func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}) hexutil.Bytes { +func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}) (hexutil.Bytes, error) { encTypes := []string{} encValues := []interface{}{} @@ -362,8 +370,13 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter encTypes = append(encTypes, "bytes32") encValues = append(encValues, typedData.TypeHash(primaryType)) + // Generate error for a mismatch between the provided type and data + dataMismatchError := func(encType string, encValue interface{}) error { + return fmt.Errorf("provided data '%v' doesn't match type '%s'", encValue, encType) + } + // Handle primitive values - handlePrimitiveValue := func(encType string, encValue interface{}) (string, interface{}) { + handlePrimitiveValue := func(encType string, encValue interface{}) (string, interface{}, error) { var primitiveEncType string var primitiveEncValue interface{} @@ -374,20 +387,32 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter for i := 0; i < 12; i++ { bytesValue = append(bytesValue, 0) } - for _, _byte := range common.HexToAddress(encValue.(string)) { + stringValue, ok := encValue.(string) + if !ok || !common.IsHexAddress(stringValue) { + return "", nil, dataMismatchError(encType, encValue) + } + for _, _byte := range common.HexToAddress(stringValue) { bytesValue = append(bytesValue, _byte) } primitiveEncValue = bytesValue case "bool": primitiveEncType = "uint256" var int64Val int64 - if encValue.(bool) { + boolValue, ok := encValue.(bool) + if !ok { + return "", nil, dataMismatchError(encType, encValue) + } + if boolValue { int64Val = 1 } primitiveEncValue = abi.U256(big.NewInt(int64Val)) case "bytes", "string": primitiveEncType = "bytes32" - primitiveEncValue = crypto.Keccak256(bytesValueOf(encValue)) + bytesValue, err := bytesValueOf(encValue) + if err != nil { + return "", nil, dataMismatchError(encType, encValue) + } + primitiveEncValue = crypto.Keccak256(bytesValue) default: if strings.HasPrefix(encType, "bytes") { encTypes = append(encTypes, "bytes32") @@ -397,14 +422,21 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter for i := 0; i < 32-size; i++ { bytesValue = append(bytesValue, 0) } + if _, ok := encValue.(hexutil.Bytes); !ok { + return "", nil, dataMismatchError(encType, encValue) + } bytesValue = append(bytesValue, encValue.(hexutil.Bytes)...) primitiveEncValue = bytesValue } else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { primitiveEncType = "uint256" - primitiveEncValue = abi.U256(encValue.(*big.Int)) + bigIntValue, ok := encValue.(*big.Int) + if !ok { + return "", nil, dataMismatchError(encType, encValue) + } + primitiveEncValue = abi.U256(bigIntValue) } } - return primitiveEncType, primitiveEncValue + return primitiveEncType, primitiveEncValue, nil } // Add field contents. Structs and arrays have special handlings. @@ -414,24 +446,49 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter 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) + mapValue, ok := item.(map[string]interface{}) + if !ok { + return nil, dataMismatchError(parsedType, item) + } + encodedData, err := typedData.EncodeData(parsedType, mapValue) + if err != nil { + return nil, err + } + arrayBuffer.Write(encodedData) } else { - _, encValue := handlePrimitiveValue(encType, encValue) - arrayBuffer.Write(bytesValueOf(encValue)) + _, encValue, err := handlePrimitiveValue(encType, encValue) + if err != nil { + return nil, err + } + bytesValue, err := bytesValueOf(encValue) + if err != nil { + return nil, err + } + arrayBuffer.Write(bytesValue) } } 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)) + mapValue, ok := encValue.(map[string]interface{}) + if !ok { + return nil, dataMismatchError(encType, encValue) + } + encodedData, err := typedData.EncodeData(field["type"], mapValue) + if err != nil { + return nil, err + } + encValue = crypto.Keccak256(encodedData) encValues = append(encValues, encValue) } else { - primitiveEncType, primitiveEncValue := handlePrimitiveValue(encType, encValue) + primitiveEncType, primitiveEncValue, err := handlePrimitiveValue(encType, encValue) + if err != nil { + return nil, err + } encTypes = append(encTypes, primitiveEncType) encValues = append(encValues, primitiveEncValue) } @@ -439,31 +496,34 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter buffer := bytes.Buffer{} for _, encValue := range encValues { - buffer.Write(bytesValueOf(encValue)) + bytesValue, err := bytesValueOf(encValue) + if err != nil { + return nil, err + } + buffer.Write(bytesValue) } - return buffer.Bytes() // https://github.com/ethereumjs/ethereumjs-abi/blob/master/lib/index.js#L336 + return buffer.Bytes(), nil // https://github.com/ethereumjs/ethereumjs-abi/blob/master/lib/index.js#L336 } -func bytesValueOf(_interface interface{}) hexutil.Bytes { +func bytesValueOf(_interface interface{}) (hexutil.Bytes, error) { bytesValue, ok := _interface.(hexutil.Bytes) if ok { - return bytesValue + return bytesValue, nil } switch reflect.TypeOf(_interface) { case reflect.TypeOf(hexutil.Bytes{}): - return _interface.(hexutil.Bytes) + return _interface.(hexutil.Bytes), nil case reflect.TypeOf([]uint8{}): - return _interface.([]uint8) + return _interface.([]uint8), nil case reflect.TypeOf(string("")): - return hexutil.Bytes(_interface.(string)) + return hexutil.Bytes(_interface.(string)), nil default: break } - panic(fmt.Errorf("unrecognized interface type %T", _interface)) - return hexutil.Bytes{} + return nil, fmt.Errorf("unrecognized interface type %T", _interface) } // EcRecover recovers the address associated with the given sig. @@ -523,6 +583,17 @@ func UnmarshalValidatorData(data interface{}) (ValidatorData, error) { }, nil } +// IsValid checks if the typed data is sound +func (typedData *TypedData) IsValid() error { + if err := typedData.Types.IsValid(); err != nil { + return err + } + if err := typedData.Domain.IsValid(); err != nil { + return err + } + return nil +} + // Map is a helper function to generate a map version of the typed data func (typedData *TypedData) Map() map[string]interface{} { dataMap := map[string]interface{}{ @@ -535,27 +606,25 @@ func (typedData *TypedData) Map() map[string]interface{} { return dataMap } -// IsValid checks if the given types object is conformant to the specs +// IsValid checks if the types object is conformant to the specs func (types *EIP712Types) IsValid() error { for typeKey, typeArr := range *types { for _, typeObj := range typeArr { typeVal := typeObj["type"] if typeKey == typeVal { - panic(fmt.Errorf("type %s cannot reference itself", typeVal)) + return fmt.Errorf("type '%s' cannot reference itself", typeVal) } - firstChar := []rune(typeVal)[0] if unicode.IsUpper(firstChar) { if (*types)[typeVal] == nil { - return fmt.Errorf("referenced type %s is undefined", typeVal) + 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) + return fmt.Errorf("referenced type '%s' must be capitalized", typeVal) } else { - return fmt.Errorf("unknown type %s", typeVal) + return fmt.Errorf("unknown atomic type '%s'", typeVal) } } } diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index abb5a7ac73c6..745a3141c00c 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -18,6 +18,7 @@ package core import ( "context" + "encoding/json" "fmt" "math/big" "testing" @@ -153,12 +154,20 @@ func TestSignData(t *testing.T) { } func TestHashStruct(t *testing.T) { - mainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(typedData.HashStruct(typedData.PrimaryType, typedData.Message))) + hash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) + if err != nil { + t.Fatal(err) + } + mainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(hash)) if mainHash != "0xc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e" { t.Errorf("Expected different hashStruct result (got %s)", mainHash) } - domainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(typedData.HashStruct("EIP712Domain", typedData.Domain.Map()))) + hash, err = typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) + if err != nil { + t.Error(err) + } + domainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(hash)) if domainHash != "0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f" { t.Errorf("Expected different hashStruct result (got %s)", domainHash) } @@ -184,9 +193,259 @@ func TestTypeHash(t *testing.T) { } func TestEncodeData(t *testing.T) { - - dataEncoding := fmt.Sprintf("0x%s", common.Bytes2Hex(typedData.EncodeData(typedData.PrimaryType, typedData.Message))) + hash, err := typedData.EncodeData(typedData.PrimaryType, typedData.Message) + if err != nil { + t.Fatal(err) + } + dataEncoding := fmt.Sprintf("0x%s", common.Bytes2Hex(hash)) if dataEncoding != "0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8cd54f074a4af31b4411ff6a60c9719dbd559c221c8ac3492d9d872b041d703d1b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8" { t.Errorf("Expected different encodeData result (got %s)", dataEncoding) } } + +func TestMalformedData1(t *testing.T) { + var data = ` + { + "types": { + "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": "Person" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": 1, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } + } + +` + var typedData TypedData + err := json.Unmarshal([]byte(data), &typedData) + if err != nil { + t.Fatalf("unmarshalling failed %v", err) + } + err = typedData.IsValid() + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + _, err = typedData.HashStruct(typedData.PrimaryType, typedData.Message) + if err.Error() != "provided data 'Hello, Bob!' doesn't match type 'Person'" { + t.Errorf("Expected `provided data 'Hello, Bob!' doesn't match type 'Person'`, got %v", err) + } +} + +func TestMalformedDomainData(t *testing.T) { + var data = ` +{ + "types": { + "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": "Blahonga" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": 1, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } + }` + var typedData TypedData + err := json.Unmarshal([]byte(data), &typedData) + if err != nil { + t.Fatalf("unmarshalling failed %v", err) + } + err = typedData.IsValid() + if err == nil { + t.Fatalf("Expected `referenced type 'Blahonga' is undefined`, got %v", err) + } + _, err = typedData.HashStruct(typedData.PrimaryType, typedData.Message) + if err.Error() != "unrecognized interface type " { + t.Errorf("Expected `unrecognized interface type `, got %v", err) + } +} + +func TestMalformedData3(t *testing.T) { + var data = ` + { + "types": { + "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" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": 1, + "vxerifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } + } + +` + var typedData TypedData + err := json.Unmarshal([]byte(data), &typedData) + if err != nil { + t.Fatalf("unmarshalling failed %v", err) + } + err = typedData.IsValid() + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + _, err = typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) + if err.Error() != "provided data '' doesn't match type 'address'" { + t.Errorf("Expected `provided data '' doesn't match type 'address'`, got %v", err) + } +} From 9364b324d3f77ec3e0155df59064ea4fdcc8c232 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Fri, 9 Nov 2018 21:00:07 +0200 Subject: [PATCH 58/84] Added alphabetical sorting to type dependencies --- signer/core/signed_data.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 95454acfdc23..9566c805693b 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -24,6 +24,7 @@ import ( "math/big" "mime" "reflect" + "sort" "strconv" "strings" "unicode" @@ -329,13 +330,9 @@ func (typedData *TypedData) Dependencies(primaryType string, found []string) []s func (typedData *TypedData) EncodeType(primaryType string) hexutil.Bytes { // 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...) + slicedDeps := deps[1:] + sort.Strings(slicedDeps) + deps = append([]string{primaryType}, slicedDeps...) // Format as a string with fields var buffer bytes.Buffer From 1b022ec3f05d0efdc24ef95901a1da131e051489 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sat, 10 Nov 2018 00:04:46 +0200 Subject: [PATCH 59/84] Added pretty print to data/typed UI --- signer/core/cliui.go | 2 +- signer/core/signed_data.go | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/signer/core/cliui.go b/signer/core/cliui.go index 1e5927b242b0..035d4f201680 100644 --- a/signer/core/cliui.go +++ b/signer/core/cliui.go @@ -164,7 +164,7 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResp fmt.Printf("-------- Sign data request--------------\n") fmt.Printf("Account: %s\n", request.Address.String()) - fmt.Printf("message: \n%q\n", request.Message) + fmt.Printf("message: \n%v\n", request.Message) fmt.Printf("raw data: \n%v\n", request.Rawdata) fmt.Printf("message hash: %v\n", request.Hash) fmt.Printf("-------------------------------------------\n") diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 9566c805693b..4bdc4b4ae66a 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -19,6 +19,7 @@ package core import ( "bytes" "context" + "encoding/json" "errors" "fmt" "math/big" @@ -275,9 +276,12 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd if err != nil { return nil, err } - msg := fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)) - sighash := crypto.Keccak256([]byte(msg)) - req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: typedData.Map(), Message: msg, Hash: sighash} + msg, err := json.MarshalIndent(typedData.Message, "", " ") + if err != nil { + return nil, err + } + sighash := crypto.Keccak256([]byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))) + req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: typedData, Message: string(msg), Hash: sighash} signature, err := api.Sign(ctx, addr, req) if err != nil { api.UI.ShowError(err.Error()) @@ -591,7 +595,7 @@ func (typedData *TypedData) IsValid() error { return nil } -// Map is a helper function to generate a map version of the typed data +// Map generates a map version of the typed data func (typedData *TypedData) Map() map[string]interface{} { dataMap := map[string]interface{}{ "types": typedData.Types, @@ -603,6 +607,11 @@ func (typedData *TypedData) Map() map[string]interface{} { return dataMap } +// PrettyPrint generates a pretty version of the typed data +func (typedData *TypedData) PrettyPrint() string { + return "" +} + // IsValid checks if the types object is conformant to the specs func (types *EIP712Types) IsValid() error { for typeKey, typeArr := range *types { From 1524475baac676cd86635c743ae5bead316c2903 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 13 Nov 2018 09:45:22 +0100 Subject: [PATCH 60/84] signer: more tests for typed data --- signer/core/signed_data_test.go | 109 ++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 745a3141c00c..4b103ea0558c 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -449,3 +449,112 @@ func TestMalformedData3(t *testing.T) { t.Errorf("Expected `provided data '' doesn't match type 'address'`, got %v", err) } } + +func TestMalformedData4(t *testing.T) { + var data = ` + { + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256 ... and now for something completely different" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Person": [ + { + "name": "name", + "type": "string" + }, + { + "name": "test", + "type": "uint8" + }, + { + "name": "wallet", + "type": "address" + } + ], + "Mail": [ + { + "name": "from", + "type": "Person" + }, + { + "name": "to", + "type": "Person" + }, + { + "name": "contents", + "type": "string" + } + ] + }, + "primaryType": "Mail", + "domain": { + "Signed by": "Bill Gates -- this text won't affect the hash'", + "we can": "stuff anything here, really", + "name": "Ether Mail", + "version": "65536", + "chainId": 1, + "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "test": 65536, + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "blahonga": "zonk bonk", + "contents": "Γ₯Γ€zΓΆ \r\n test, Bob!" + } + } +` + // The struct above contains several quirks + // 1. Using dynamic types and only validating the prefix: + //{ + // "name": "chainId", + // "type": "uint256 ... and now for something completely different" + //}, + // 2. Using dynamic types, but not verifying that the data fits into it + // "test": 65536, <-- test defined as uint8 + // 3a. Extra data in message + // "blahonga": "zonk bonk", + // 3b ... and in domain + // "Signed by": "Bill Gates", + + var typedData TypedData + err := json.Unmarshal([]byte(data), &typedData) + if err != nil { + t.Fatalf("unmarshalling failed %v", err) + } + err = typedData.IsValid() + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + hash, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) + if err == nil{ + t.Errorf("Expected error, got hash %v", hash) + }else + { + fmt.Printf("err %v", err) + } + //if err.Error() != "provided data '' doesn't match type 'address'" { + // t.Errorf("Expected `provided data '' doesn't match type 'address'`, got %v", err) + //} +} From 7419d513f3dab8483ea106e978a5a76908a1b2fc Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Tue, 13 Nov 2018 18:10:24 +0200 Subject: [PATCH 61/84] Fixed TestMalformedData4 errors and renamed IsValid to Validate --- signer/core/signed_data.go | 48 ++++++++++++++------------------- signer/core/signed_data_test.go | 22 +++++---------- 2 files changed, 26 insertions(+), 44 deletions(-) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 4bdc4b4ae66a..68d35768452c 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -25,6 +25,7 @@ import ( "math/big" "mime" "reflect" + "regexp" "sort" "strconv" "strings" @@ -265,7 +266,7 @@ func SignTextPlain(data hexutil.Bytes) (hexutil.Bytes, string) { // 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) { - if err := typedData.IsValid(); err != nil { + if err := typedData.Validate(); err != nil { return nil, err } domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) @@ -584,12 +585,12 @@ func UnmarshalValidatorData(data interface{}) (ValidatorData, error) { }, nil } -// IsValid checks if the typed data is sound -func (typedData *TypedData) IsValid() error { - if err := typedData.Types.IsValid(); err != nil { +// Validate checks if the typed data is sound +func (typedData *TypedData) Validate() error { + if err := typedData.Types.Validate(); err != nil { return err } - if err := typedData.Domain.IsValid(); err != nil { + if err := typedData.Domain.Validate(); err != nil { return err } return nil @@ -612,8 +613,8 @@ func (typedData *TypedData) PrettyPrint() string { return "" } -// IsValid checks if the types object is conformant to the specs -func (types *EIP712Types) IsValid() error { +// Validate checks if the types object is conformant to the specs +func (types *EIP712Types) Validate() error { for typeKey, typeArr := range *types { for _, typeObj := range typeArr { typeVal := typeObj["type"] @@ -642,35 +643,26 @@ func (types *EIP712Types) IsValid() error { // isStandardType checks if the given type is a EIP712 conformant type func isStandardTypeStr(encType string) bool { // Atomic types - for _, standardType := range []string{ - TypeAddress, - TypeBool, - TypeBytes, - TypeString, - } { - if standardType == encType { - return true - } + exp, _ := regexp.Compile(`^(address|bool|bytes|string)$`) + if (exp.MatchString(encType)) { + return true } // Dynamic types - for _, standardType := range []string{ - TypeBytes, - TypeInt, - TypeUint, - } { - if strings.HasPrefix(encType, standardType) { - return true - } + exp, _ = regexp.Compile(`^(bytes|int|uint)(\d+)$`) + if (exp.MatchString(encType)) { + return true } - // Reference types - return encType[len(encType)-1] == ']' + // Arrays + // TODO: add dynamic type arrays + exp, _ = regexp.Compile(`^(address|bool|bytes|string)\[]$`) + return exp.MatchString(encType) } -// IsValid checks if the given domain is valid, i.e. contains at least +// Validate checks if the given domain is valid, i.e. contains at least // the minimum viable keys and values -func (domain *EIP712Domain) IsValid() error { +func (domain *EIP712Domain) Validate() error { if domain.ChainId == big.NewInt(0) { return errors.New("chainId must be specified according to EIP-155") } diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 4b103ea0558c..697fd0e35e79 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -276,7 +276,7 @@ func TestMalformedData1(t *testing.T) { if err != nil { t.Fatalf("unmarshalling failed %v", err) } - err = typedData.IsValid() + err = typedData.Validate() if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -357,7 +357,7 @@ func TestMalformedDomainData(t *testing.T) { if err != nil { t.Fatalf("unmarshalling failed %v", err) } - err = typedData.IsValid() + err = typedData.Validate() if err == nil { t.Fatalf("Expected `referenced type 'Blahonga' is undefined`, got %v", err) } @@ -440,7 +440,7 @@ func TestMalformedData3(t *testing.T) { if err != nil { t.Fatalf("unmarshalling failed %v", err) } - err = typedData.IsValid() + err = typedData.Validate() if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -543,18 +543,8 @@ func TestMalformedData4(t *testing.T) { if err != nil { t.Fatalf("unmarshalling failed %v", err) } - err = typedData.IsValid() - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - hash, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) - if err == nil{ - t.Errorf("Expected error, got hash %v", hash) - }else - { - fmt.Printf("err %v", err) + err = typedData.Validate() + if err.Error() != "unknown atomic type 'uint256 ... and now for something completely different'" { + t.Fatalf("Expected `unknown atomic type 'uint256 ... and now for something completely different'`, got %v", err) } - //if err.Error() != "provided data '' doesn't match type 'address'" { - // t.Errorf("Expected `provided data '' doesn't match type 'address'`, got %v", err) - //} } From 9855fdeec82d48010ef4a329b5c8bc81297f23be Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Wed, 14 Nov 2018 00:25:18 +0200 Subject: [PATCH 62/84] Fixed more new failing tests and deanonymised some functions --- signer/core/signed_data.go | 173 +++++++++++++++----------------- signer/core/signed_data_test.go | 156 +++++++++++++++++----------- 2 files changed, 180 insertions(+), 149 deletions(-) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 68d35768452c..5cb2b3ebe8be 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -365,92 +365,28 @@ func (typedData *TypedData) TypeHash(primaryType string) hexutil.Bytes { // // each encoded member is 32-byte long func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}) (hexutil.Bytes, error) { - encTypes := []string{} encValues := []interface{}{} - // Add typehash - encTypes = append(encTypes, "bytes32") - encValues = append(encValues, typedData.TypeHash(primaryType)) - - // Generate error for a mismatch between the provided type and data - dataMismatchError := func(encType string, encValue interface{}) error { - return fmt.Errorf("provided data '%v' doesn't match type '%s'", encValue, encType) + // Verify extra data + if len(typedData.Types[primaryType]) < len(data) { + return nil, errors.New("there is extra data provided in the message") } - // Handle primitive values - handlePrimitiveValue := func(encType string, encValue interface{}) (string, interface{}, error) { - var primitiveEncType string - var primitiveEncValue interface{} - - switch encType { - case "address": - primitiveEncType = "uint160" - bytesValue := hexutil.Bytes{} - for i := 0; i < 12; i++ { - bytesValue = append(bytesValue, 0) - } - stringValue, ok := encValue.(string) - if !ok || !common.IsHexAddress(stringValue) { - return "", nil, dataMismatchError(encType, encValue) - } - for _, _byte := range common.HexToAddress(stringValue) { - bytesValue = append(bytesValue, _byte) - } - primitiveEncValue = bytesValue - case "bool": - primitiveEncType = "uint256" - var int64Val int64 - boolValue, ok := encValue.(bool) - if !ok { - return "", nil, dataMismatchError(encType, encValue) - } - if boolValue { - int64Val = 1 - } - primitiveEncValue = abi.U256(big.NewInt(int64Val)) - case "bytes", "string": - primitiveEncType = "bytes32" - bytesValue, err := bytesValueOf(encValue) - if err != nil { - return "", nil, dataMismatchError(encType, encValue) - } - primitiveEncValue = crypto.Keccak256(bytesValue) - default: - if strings.HasPrefix(encType, "bytes") { - encTypes = append(encTypes, "bytes32") - sizeStr := strings.TrimPrefix(encType, "bytes") - size, _ := strconv.Atoi(sizeStr) - bytesValue := hexutil.Bytes{} - for i := 0; i < 32-size; i++ { - bytesValue = append(bytesValue, 0) - } - if _, ok := encValue.(hexutil.Bytes); !ok { - return "", nil, dataMismatchError(encType, encValue) - } - bytesValue = append(bytesValue, encValue.(hexutil.Bytes)...) - primitiveEncValue = bytesValue - } else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { - primitiveEncType = "uint256" - bigIntValue, ok := encValue.(*big.Int) - if !ok { - return "", nil, dataMismatchError(encType, encValue) - } - primitiveEncValue = abi.U256(bigIntValue) - } - } - return primitiveEncType, primitiveEncValue, nil - } + // Add typehash + encValues = append(encValues, typedData.TypeHash(primaryType)) - // Add field contents. Structs and arrays have special handlings. + // Add field contents. Structs and arrays have special handlers. for _, field := range typedData.Types[primaryType] { encType := field["type"] encValue := data[field["name"]] if encType[len(encType)-1:] == "]" { - encTypes = append(encTypes, "bytes32") + arrayValue, ok := encValue.([]interface{}) + if !ok { + return nil, dataMismatchError(encType, encValue) + } parsedType := strings.Split(encType, "[")[0] - arrayBuffer := bytes.Buffer{} - for _, item := range encValue.([]interface{}) { + for _, item := range arrayValue { if typedData.Types[parsedType] != nil { mapValue, ok := item.(map[string]interface{}) if !ok { @@ -462,7 +398,7 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter } arrayBuffer.Write(encodedData) } else { - _, encValue, err := handlePrimitiveValue(encType, encValue) + encValue, err := handlePrimitiveValue(encType, encValue) if err != nil { return nil, err } @@ -475,7 +411,6 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter } encValues = append(encValues, crypto.Keccak256(arrayBuffer.Bytes())) } else if typedData.Types[field["type"]] != nil { - encTypes = append(encTypes, "bytes32") mapValue, ok := encValue.(map[string]interface{}) if !ok { return nil, dataMismatchError(encType, encValue) @@ -487,11 +422,10 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter encValue = crypto.Keccak256(encodedData) encValues = append(encValues, encValue) } else { - primitiveEncType, primitiveEncValue, err := handlePrimitiveValue(encType, encValue) + primitiveEncValue, err := handlePrimitiveValue(encType, encValue) if err != nil { return nil, err } - encTypes = append(encTypes, primitiveEncType) encValues = append(encValues, primitiveEncValue) } } @@ -508,6 +442,72 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter return buffer.Bytes(), nil // https://github.com/ethereumjs/ethereumjs-abi/blob/master/lib/index.js#L336 } +// handlePrimitiveValues deals with the primitive values found +// while searching through the typed data +func handlePrimitiveValue(encType string, encValue interface{}) (interface{}, error) { + var primitiveEncValue interface{} + + switch encType { + case "address": + bytesValue := hexutil.Bytes{} + for i := 0; i < 12; i++ { + bytesValue = append(bytesValue, 0) + } + stringValue, ok := encValue.(string) + if !ok || !common.IsHexAddress(stringValue) { + return nil, dataMismatchError(encType, encValue) + } + for _, _byte := range common.HexToAddress(stringValue) { + bytesValue = append(bytesValue, _byte) + } + primitiveEncValue = bytesValue + case "bool": + var int64Val int64 + boolValue, ok := encValue.(bool) + if !ok { + return nil, dataMismatchError(encType, encValue) + } + if boolValue { + int64Val = 1 + } + primitiveEncValue = abi.U256(big.NewInt(int64Val)) + case "bytes", "string": + bytesValue, err := bytesValueOf(encValue) + if err != nil { + return nil, dataMismatchError(encType, encValue) + } + primitiveEncValue = crypto.Keccak256(bytesValue) + default: + if strings.HasPrefix(encType, "bytes") { + sizeStr := strings.TrimPrefix(encType, "bytes") + size, _ := strconv.Atoi(sizeStr) + bytesValue := hexutil.Bytes{} + for i := 0; i < 32-size; i++ { + bytesValue = append(bytesValue, 0) + } + if _, ok := encValue.(hexutil.Bytes); !ok { + return nil, dataMismatchError(encType, encValue) + } + bytesValue = append(bytesValue, encValue.(hexutil.Bytes)...) + primitiveEncValue = bytesValue + } else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { + bigIntValue, ok := encValue.(*big.Int) + if !ok { + return nil, dataMismatchError(encType, encValue) + } + primitiveEncValue = abi.U256(bigIntValue) + } + } + return primitiveEncValue, nil +} + +// dataMismatchError generates an error for a mismatch between +// the provided type and data +func dataMismatchError(encType string, encValue interface{}) error { + return fmt.Errorf("provided data '%v' doesn't match type '%s'", encValue, encType) +} + +// bytesValuesOf returns the bytes value of the given interface func bytesValueOf(_interface interface{}) (hexutil.Bytes, error) { bytesValue, ok := _interface.(hexutil.Bytes) if ok { @@ -585,7 +585,7 @@ func UnmarshalValidatorData(data interface{}) (ValidatorData, error) { }, nil } -// Validate checks if the typed data is sound +// Validate make sure the types are sound func (typedData *TypedData) Validate() error { if err := typedData.Types.Validate(); err != nil { return err @@ -604,15 +604,9 @@ func (typedData *TypedData) Map() map[string]interface{} { "primaryType": typedData.PrimaryType, "message": typedData.Message, } - return dataMap } -// PrettyPrint generates a pretty version of the typed data -func (typedData *TypedData) PrettyPrint() string { - return "" -} - // Validate checks if the types object is conformant to the specs func (types *EIP712Types) Validate() error { for typeKey, typeArr := range *types { @@ -644,19 +638,18 @@ func (types *EIP712Types) Validate() error { func isStandardTypeStr(encType string) bool { // Atomic types exp, _ := regexp.Compile(`^(address|bool|bytes|string)$`) - if (exp.MatchString(encType)) { + if exp.MatchString(encType) { return true } // Dynamic types exp, _ = regexp.Compile(`^(bytes|int|uint)(\d+)$`) - if (exp.MatchString(encType)) { + if exp.MatchString(encType) { return true } // Arrays - // TODO: add dynamic type arrays - exp, _ = regexp.Compile(`^(address|bool|bytes|string)\[]$`) + exp, _ = regexp.Compile(`^(address|bool|bytes|string|((bytes|int|uint)(\d+)))\[]$`) return exp.MatchString(encType) } diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 697fd0e35e79..5e130db1f42a 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -204,7 +204,14 @@ func TestEncodeData(t *testing.T) { } func TestMalformedData1(t *testing.T) { - var data = ` + // Verifies that malformed domain keys are properly caught: + //{ + // "name": "Ether Mail", + // "version": "1", + // "chainId": 1, + // "vxerifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + //} + var jsonTypedData = ` { "types": { "EIP712Domain": [ @@ -246,7 +253,7 @@ func TestMalformedData1(t *testing.T) { }, { "name": "contents", - "type": "Person" + "type": "string" } ] }, @@ -255,7 +262,7 @@ func TestMalformedData1(t *testing.T) { "name": "Ether Mail", "version": "1", "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + "vxerifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" }, "message": { "from": { @@ -271,24 +278,39 @@ func TestMalformedData1(t *testing.T) { } ` - var typedData TypedData - err := json.Unmarshal([]byte(data), &typedData) + var malformedTypedData TypedData + err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) if err != nil { t.Fatalf("unmarshalling failed %v", err) } - err = typedData.Validate() + err = malformedTypedData.Validate() if err != nil { t.Fatalf("Expected no error, got %v", err) } - _, err = typedData.HashStruct(typedData.PrimaryType, typedData.Message) - if err.Error() != "provided data 'Hello, Bob!' doesn't match type 'Person'" { - t.Errorf("Expected `provided data 'Hello, Bob!' doesn't match type 'Person'`, got %v", err) + _, err = malformedTypedData.HashStruct("EIP712Domain", malformedTypedData.Domain.Map()) + if err == nil || err.Error() != "provided data '' doesn't match type 'address'" { + t.Errorf("Expected `provided data '' doesn't match type 'address'`, got %v", err) } } -func TestMalformedDomainData(t *testing.T) { - var data = ` -{ +func TestMalformedData2(t *testing.T) { + // Verifies that: + // 1. Mismatches between the given type and data, i.e. `Person` and + // the data item is a string, are properly caught: + //{ + // "name": "contents", + // "type": "Person" + //}, + //{ + // "contents": "Hello, Bob!" <-- string not "Person" + //} + // 2. Nonexistent types are properly caught: + //{ + // "name": "contents", + // "type": "Blahonga" + //} + jsonTypedData := ` + { "types": { "EIP712Domain": [ { @@ -329,7 +351,7 @@ func TestMalformedDomainData(t *testing.T) { }, { "name": "contents", - "type": "Blahonga" + "type": "Person" } ] }, @@ -351,24 +373,45 @@ func TestMalformedDomainData(t *testing.T) { }, "contents": "Hello, Bob!" } - }` - var typedData TypedData - err := json.Unmarshal([]byte(data), &typedData) + } +` + var malformedTypedData TypedData + err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) if err != nil { t.Fatalf("unmarshalling failed %v", err) } - err = typedData.Validate() - if err == nil { + err = malformedTypedData.Validate() + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) + if err.Error() != "provided data 'Hello, Bob!' doesn't match type 'Person'" { + t.Errorf("Expected `provided data 'Hello, Bob!' doesn't match type 'Person'`, got %v", err) + } + + malformedTypedData.Types["Mail"][2]["type"] = "Blahonga" + err = malformedTypedData.Validate() + if err == nil || err.Error() != "referenced type 'Blahonga' is undefined" { t.Fatalf("Expected `referenced type 'Blahonga' is undefined`, got %v", err) } - _, err = typedData.HashStruct(typedData.PrimaryType, typedData.Message) - if err.Error() != "unrecognized interface type " { + _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) + if err == nil || err.Error() != "unrecognized interface type " { t.Errorf("Expected `unrecognized interface type `, got %v", err) } } func TestMalformedData3(t *testing.T) { - var data = ` + // Verifies several quirks + // 1. Using dynamic types and only validating the prefix: + //{ + // "name": "chainId", + // "type": "uint256 ... and now for something completely different" + //} + // 2. Extra data in message: + //{ + // "blahonga": "zonk bonk" + //} + jsonTypedData := ` { "types": { "EIP712Domain": [ @@ -382,7 +425,7 @@ func TestMalformedData3(t *testing.T) { }, { "name": "chainId", - "type": "uint256" + "type": "uint256 ... and now for something completely different" }, { "name": "verifyingContract", @@ -419,12 +462,12 @@ func TestMalformedData3(t *testing.T) { "name": "Ether Mail", "version": "1", "chainId": 1, - "vxerifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" }, "message": { "from": { "name": "Cow", - "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + "wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" }, "to": { "name": "Bob", @@ -433,25 +476,31 @@ func TestMalformedData3(t *testing.T) { "contents": "Hello, Bob!" } } - ` - var typedData TypedData - err := json.Unmarshal([]byte(data), &typedData) + var malformedTypedData TypedData + err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) if err != nil { t.Fatalf("unmarshalling failed %v", err) } - err = typedData.Validate() - if err != nil { - t.Fatalf("Expected no error, got %v", err) + err = malformedTypedData.Validate() + if err == nil || err.Error() != "unknown atomic type 'uint256 ... and now for something completely different'" { + t.Fatalf("Expected `unknown atomic type 'uint256 ... and now for something completely different'`, got %v", err) } - _, err = typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) - if err.Error() != "provided data '' doesn't match type 'address'" { - t.Errorf("Expected `provided data '' doesn't match type 'address'`, got %v", err) + + malformedTypedData.Types["EIP712Domain"][2]["type"] = "uint256" + malformedTypedData.Message["blahonga"] = "zonk bonk" + _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) + if err == nil || err.Error() != "there is extra data provided in the message" { + t.Errorf("Expected `there is extra data provided in the message`, got %v", err) } } func TestMalformedData4(t *testing.T) { - var data = ` + // Verifies data that doesn't fit into it: + //{ + // "test": 65536 <-- test defined as uint8 + //} + jsonTypedData := ` { "types": { "EIP712Domain": [ @@ -465,7 +514,7 @@ func TestMalformedData4(t *testing.T) { }, { "name": "chainId", - "type": "uint256 ... and now for something completely different" + "type": "uint256" }, { "name": "verifyingContract", @@ -503,48 +552,37 @@ func TestMalformedData4(t *testing.T) { }, "primaryType": "Mail", "domain": { - "Signed by": "Bill Gates -- this text won't affect the hash'", - "we can": "stuff anything here, really", "name": "Ether Mail", - "version": "65536", + "version": "1", "chainId": 1, "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" }, "message": { "from": { "name": "Cow", + "test": 65536, "wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" }, "to": { "name": "Bob", - "test": 65536, "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" }, - "blahonga": "zonk bonk", - "contents": "Γ₯Γ€zΓΆ \r\n test, Bob!" + "contents": "Hello, Bob!" } } ` - // The struct above contains several quirks - // 1. Using dynamic types and only validating the prefix: - //{ - // "name": "chainId", - // "type": "uint256 ... and now for something completely different" - //}, - // 2. Using dynamic types, but not verifying that the data fits into it - // "test": 65536, <-- test defined as uint8 - // 3a. Extra data in message - // "blahonga": "zonk bonk", - // 3b ... and in domain - // "Signed by": "Bill Gates", - - var typedData TypedData - err := json.Unmarshal([]byte(data), &typedData) + var malformedTypedData TypedData + err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) if err != nil { t.Fatalf("unmarshalling failed %v", err) } - err = typedData.Validate() - if err.Error() != "unknown atomic type 'uint256 ... and now for something completely different'" { - t.Fatalf("Expected `unknown atomic type 'uint256 ... and now for something completely different'`, got %v", err) + err = malformedTypedData.Validate() + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) + if err == nil || err.Error() != "provided data '65536' doesn't match type 'uint8'" { + t.Fatalf("Expected `provided data '65536' doesn't match type 'uint8'`, got %v", err) } } From 3965acc13a3e1b50e50fb8d56ed72475d2b65ec0 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Wed, 14 Nov 2018 14:16:23 +0200 Subject: [PATCH 63/84] Added types to EIP712 output in cliui --- signer/core/signed_data.go | 54 +++++++++++++++++++++++---------- signer/core/signed_data_test.go | 6 ++-- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 5cb2b3ebe8be..f2c8bc005787 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -19,7 +19,6 @@ package core import ( "bytes" "context" - "encoding/json" "errors" "fmt" "math/big" @@ -75,6 +74,7 @@ type TypedData struct { PrimaryType string `json:"primaryType"` Domain EIP712Domain `json:"domain"` Message EIP712Data `json:"message"` + Output bytes.Buffer } type EIP712Type []map[string]string @@ -273,16 +273,16 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd if err != nil { return nil, err } + typedData.Output.Reset() + typedData.Output.WriteString(fmt.Sprintf("%s {\n", typedData.PrimaryType)) typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) if err != nil { return nil, err } - msg, err := json.MarshalIndent(typedData.Message, "", " ") - if err != nil { - return nil, err - } + typedData.Output.Truncate(typedData.Output.Len() - 2) + typedData.Output.WriteString(fmt.Sprintf("\n}")) sighash := crypto.Keccak256([]byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))) - req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: typedData, Message: string(msg), Hash: sighash} + req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: typedData.Map(), Message: typedData.Output.String(), Hash: sighash} signature, err := api.Sign(ctx, addr, req) if err != nil { api.UI.ShowError(err.Error()) @@ -293,7 +293,7 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd // HashStruct generates a keccak256 hash of the encoding of the provided data func (typedData *TypedData) HashStruct(primaryType string, data EIP712Data) (hexutil.Bytes, error) { - encodedData, err := typedData.EncodeData(primaryType, data) + encodedData, err := typedData.EncodeData(primaryType, data, 1) if err != nil { return nil, err } @@ -364,7 +364,7 @@ func (typedData *TypedData) TypeHash(primaryType string) hexutil.Bytes { // `enc(value₁) β€– enc(valueβ‚‚) β€– … β€– enc(valueβ‚™)` // // each encoded member is 32-byte long -func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}) (hexutil.Bytes, error) { +func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}, depth int) (hexutil.Bytes, error) { encValues := []interface{}{} // Verify extra data @@ -384,21 +384,24 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter if !ok { return nil, dataMismatchError(encType, encValue) } - parsedType := strings.Split(encType, "[")[0] + typedData.Output.WriteString(strings.Repeat("\u00a0", depth*2)) + typedData.Output.WriteString(fmt.Sprintf("\"%s\": [ %s\n", field["name"], encType)) + arrayBuffer := bytes.Buffer{} + parsedType := strings.Split(encType, "[")[0] for _, item := range arrayValue { if typedData.Types[parsedType] != nil { mapValue, ok := item.(map[string]interface{}) if !ok { return nil, dataMismatchError(parsedType, item) } - encodedData, err := typedData.EncodeData(parsedType, mapValue) + encodedData, err := typedData.EncodeData(parsedType, mapValue, depth+1) if err != nil { return nil, err } arrayBuffer.Write(encodedData) } else { - encValue, err := handlePrimitiveValue(encType, encValue) + encValue, err := typedData.HandlePrimitiveValue(encType, encValue, depth) if err != nil { return nil, err } @@ -409,20 +412,29 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter arrayBuffer.Write(bytesValue) } } + + typedData.Output.Truncate(typedData.Output.Len() - 2) + typedData.Output.WriteString(fmt.Sprintf("\n%s],\n", strings.Repeat("\u00a0", depth*2))) encValues = append(encValues, crypto.Keccak256(arrayBuffer.Bytes())) } else if typedData.Types[field["type"]] != nil { mapValue, ok := encValue.(map[string]interface{}) if !ok { return nil, dataMismatchError(encType, encValue) } - encodedData, err := typedData.EncodeData(field["type"], mapValue) + typedData.Output.WriteString(strings.Repeat("\u00a0", depth*2)) + typedData.Output.WriteString(fmt.Sprintf("\"%s\": { %s\n", field["name"], encType)) + + encodedData, err := typedData.EncodeData(field["type"], mapValue, depth+1) if err != nil { return nil, err } + + typedData.Output.Truncate(typedData.Output.Len() - 2) + typedData.Output.WriteString(fmt.Sprintf("\n%s},\n", strings.Repeat("\u00a0", depth*2))) encValue = crypto.Keccak256(encodedData) encValues = append(encValues, encValue) } else { - primitiveEncValue, err := handlePrimitiveValue(encType, encValue) + primitiveEncValue, err := typedData.HandlePrimitiveValue(encType, encValue, depth) if err != nil { return nil, err } @@ -444,9 +456,11 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter // handlePrimitiveValues deals with the primitive values found // while searching through the typed data -func handlePrimitiveValue(encType string, encValue interface{}) (interface{}, error) { +func (typedData *TypedData) HandlePrimitiveValue(encType string, encValue interface{}, depth int) (interface{}, error) { var primitiveEncValue interface{} + typedData.Output.WriteString(strings.Repeat("\u00a0", depth*2)) + typedData.Output.WriteString(fmt.Sprintf("\"%s\": ", encType)) switch encType { case "address": bytesValue := hexutil.Bytes{} @@ -457,10 +471,12 @@ func handlePrimitiveValue(encType string, encValue interface{}) (interface{}, er if !ok || !common.IsHexAddress(stringValue) { return nil, dataMismatchError(encType, encValue) } - for _, _byte := range common.HexToAddress(stringValue) { + addressValue := common.HexToAddress(stringValue) + for _, _byte := range addressValue { bytesValue = append(bytesValue, _byte) } primitiveEncValue = bytesValue + typedData.Output.WriteString(fmt.Sprintf("%s,\n", addressValue.String())) case "bool": var int64Val int64 boolValue, ok := encValue.(bool) @@ -471,12 +487,14 @@ func handlePrimitiveValue(encType string, encValue interface{}) (interface{}, er int64Val = 1 } primitiveEncValue = abi.U256(big.NewInt(int64Val)) + typedData.Output.WriteString(fmt.Sprintf("%t,\n", boolValue)) case "bytes", "string": bytesValue, err := bytesValueOf(encValue) if err != nil { return nil, dataMismatchError(encType, encValue) } primitiveEncValue = crypto.Keccak256(bytesValue) + typedData.Output.WriteString(fmt.Sprintf("\"%s\",\n", encValue)) default: if strings.HasPrefix(encType, "bytes") { sizeStr := strings.TrimPrefix(encType, "bytes") @@ -490,12 +508,16 @@ func handlePrimitiveValue(encType string, encValue interface{}) (interface{}, er } bytesValue = append(bytesValue, encValue.(hexutil.Bytes)...) primitiveEncValue = bytesValue + typedData.Output.WriteString(fmt.Sprintf("\"%s\",\n", encValue)) } else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { bigIntValue, ok := encValue.(*big.Int) if !ok { return nil, dataMismatchError(encType, encValue) } primitiveEncValue = abi.U256(bigIntValue) + typedData.Output.WriteString(fmt.Sprintf("%d,\n", bigIntValue)) + } else { + return nil, fmt.Errorf("unrecognized type '%s'", encType) } } return primitiveEncValue, nil @@ -525,7 +547,7 @@ func bytesValueOf(_interface interface{}) (hexutil.Bytes, error) { break } - return nil, fmt.Errorf("unrecognized interface type %T", _interface) + return nil, fmt.Errorf("unrecognized type '%T'", _interface) } // EcRecover recovers the address associated with the given sig. diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 5e130db1f42a..936f0fbb3695 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -193,7 +193,7 @@ func TestTypeHash(t *testing.T) { } func TestEncodeData(t *testing.T) { - hash, err := typedData.EncodeData(typedData.PrimaryType, typedData.Message) + hash, err := typedData.EncodeData(typedData.PrimaryType, typedData.Message, 0) if err != nil { t.Fatal(err) } @@ -395,8 +395,8 @@ func TestMalformedData2(t *testing.T) { t.Fatalf("Expected `referenced type 'Blahonga' is undefined`, got %v", err) } _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) - if err == nil || err.Error() != "unrecognized interface type " { - t.Errorf("Expected `unrecognized interface type `, got %v", err) + if err == nil || err.Error() != "unrecognized type 'Blahonga'" { + t.Errorf("Expected `unrecognized type 'Blahonga'`, got %v", err) } } From 4d9d28b0d05e1ac5c8e1c932dfbe95d6ed269de1 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Tue, 20 Nov 2018 11:47:09 +0200 Subject: [PATCH 64/84] Fixed regexp issues --- signer/core/signed_data.go | 88 ++++++++++++--------------------- signer/core/signed_data_test.go | 6 +-- 2 files changed, 35 insertions(+), 59 deletions(-) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index f2c8bc005787..2d26ec80493e 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -70,25 +70,25 @@ type ValidatorData struct { } type TypedData struct { - Types EIP712Types `json:"types"` - PrimaryType string `json:"primaryType"` - Domain EIP712Domain `json:"domain"` - Message EIP712Data `json:"message"` + Types Types `json:"types"` + PrimaryType string `json:"primaryType"` + Domain TypedDataDomain `json:"domain"` + Message TypedDataMessage `json:"message"` Output bytes.Buffer } -type EIP712Type []map[string]string +type Type []map[string]string -type EIP712Types map[string]EIP712Type +type Types map[string]Type -type EIP712TypePriority struct { +type TypePriority struct { Type string Value uint } -type EIP712Data = map[string]interface{} +type TypedDataMessage = map[string]interface{} -type EIP712Domain struct { +type TypedDataDomain struct { Name string `json:"name"` Version string `json:"version"` ChainId *big.Int `json:"chainId"` @@ -96,14 +96,7 @@ type EIP712Domain struct { Salt string `json:"salt"` } -const ( - TypeAddress = "address" - TypeBool = "bool" - TypeBytes = "bytes" - TypeInt = "int" - TypeString = "string" - TypeUint = "uint" -) +var typedDataRegexp = regexp.MustCompile(`^((address|bool|bytes|string)|((bytes)([1-9]|[1-2][0-9]|3[0-2]))|((int|uint)(8|16|32|64|128|256)))(\[])?$`) // Sign receives a request and produces a signature @@ -292,7 +285,7 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd } // HashStruct generates a keccak256 hash of the encoding of the provided data -func (typedData *TypedData) HashStruct(primaryType string, data EIP712Data) (hexutil.Bytes, error) { +func (typedData *TypedData) HashStruct(primaryType string, data TypedDataMessage) (hexutil.Bytes, error) { encodedData, err := typedData.EncodeData(primaryType, data, 1) if err != nil { return nil, err @@ -365,7 +358,7 @@ func (typedData *TypedData) TypeHash(primaryType string) hexutil.Bytes { // // each encoded member is 32-byte long func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}, depth int) (hexutil.Bytes, error) { - encValues := []interface{}{} + buffer := bytes.Buffer{} // Verify extra data if len(typedData.Types[primaryType]) < len(data) { @@ -373,7 +366,7 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter } // Add typehash - encValues = append(encValues, typedData.TypeHash(primaryType)) + buffer.Write(typedData.TypeHash(primaryType)) // Add field contents. Structs and arrays have special handlers. for _, field := range typedData.Types[primaryType] { @@ -415,7 +408,7 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter typedData.Output.Truncate(typedData.Output.Len() - 2) typedData.Output.WriteString(fmt.Sprintf("\n%s],\n", strings.Repeat("\u00a0", depth*2))) - encValues = append(encValues, crypto.Keccak256(arrayBuffer.Bytes())) + buffer.Write(crypto.Keccak256(arrayBuffer.Bytes())) } else if typedData.Types[field["type"]] != nil { mapValue, ok := encValue.(map[string]interface{}) if !ok { @@ -431,24 +424,18 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter typedData.Output.Truncate(typedData.Output.Len() - 2) typedData.Output.WriteString(fmt.Sprintf("\n%s},\n", strings.Repeat("\u00a0", depth*2))) - encValue = crypto.Keccak256(encodedData) - encValues = append(encValues, encValue) + buffer.Write(crypto.Keccak256(encodedData)) } else { primitiveEncValue, err := typedData.HandlePrimitiveValue(encType, encValue, depth) if err != nil { return nil, err } - encValues = append(encValues, primitiveEncValue) - } - } - - buffer := bytes.Buffer{} - for _, encValue := range encValues { - bytesValue, err := bytesValueOf(encValue) - if err != nil { - return nil, err + bytesValue, err := bytesValueOf(primitiveEncValue) + if err != nil { + return nil, err + } + buffer.Write(bytesValue) } - buffer.Write(bytesValue) } return buffer.Bytes(), nil // https://github.com/ethereumjs/ethereumjs-abi/blob/master/lib/index.js#L336 @@ -539,6 +526,8 @@ func bytesValueOf(_interface interface{}) (hexutil.Bytes, error) { switch reflect.TypeOf(_interface) { case reflect.TypeOf(hexutil.Bytes{}): return _interface.(hexutil.Bytes), nil + case reflect.TypeOf([]byte{}): + return hexutil.Bytes(_interface.([]byte)), nil case reflect.TypeOf([]uint8{}): return _interface.([]uint8), nil case reflect.TypeOf(string("")): @@ -584,6 +573,9 @@ func UnmarshalValidatorData(data interface{}) (ValidatorData, error) { raw := data.(map[string]interface{}) addr, ok := raw["address"].(string) + if !ok { + return ValidatorData{}, errors.New("validator address is not sent as a string") + } addrBytes, err := hexutil.Decode(addr) if err != nil { return ValidatorData{}, err @@ -593,6 +585,9 @@ func UnmarshalValidatorData(data interface{}) (ValidatorData, error) { } message, ok := raw["message"].(string) + if !ok { + return ValidatorData{}, errors.New("message is not sent as a string") + } messageBytes, err := hexutil.Decode(message) if err != nil { return ValidatorData{}, err @@ -630,7 +625,7 @@ func (typedData *TypedData) Map() map[string]interface{} { } // Validate checks if the types object is conformant to the specs -func (types *EIP712Types) Validate() error { +func (types *Types) Validate() error { for typeKey, typeArr := range *types { for _, typeObj := range typeArr { typeVal := typeObj["type"] @@ -643,7 +638,7 @@ func (types *EIP712Types) Validate() error { return fmt.Errorf("referenced type '%s' is undefined", typeVal) } } else { - if !isStandardTypeStr(typeVal) { + if !typedDataRegexp.MatchString(typeVal) { if (*types)[typeVal] != nil { return fmt.Errorf("referenced type '%s' must be capitalized", typeVal) } else { @@ -656,28 +651,9 @@ func (types *EIP712Types) Validate() error { return nil } -// isStandardType checks if the given type is a EIP712 conformant type -func isStandardTypeStr(encType string) bool { - // Atomic types - exp, _ := regexp.Compile(`^(address|bool|bytes|string)$`) - if exp.MatchString(encType) { - return true - } - - // Dynamic types - exp, _ = regexp.Compile(`^(bytes|int|uint)(\d+)$`) - if exp.MatchString(encType) { - return true - } - - // Arrays - exp, _ = regexp.Compile(`^(address|bool|bytes|string|((bytes|int|uint)(\d+)))\[]$`) - return exp.MatchString(encType) -} - // Validate checks if the given domain is valid, i.e. contains at least // the minimum viable keys and values -func (domain *EIP712Domain) Validate() error { +func (domain *TypedDataDomain) Validate() error { if domain.ChainId == big.NewInt(0) { return errors.New("chainId must be specified according to EIP-155") } @@ -690,7 +666,7 @@ func (domain *EIP712Domain) Validate() error { } // Map is a helper function to generate a map version of the domain -func (domain *EIP712Domain) Map() map[string]interface{} { +func (domain *TypedDataDomain) Map() map[string]interface{} { dataMap := map[string]interface{}{ "chainId": domain.ChainId, } diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 936f0fbb3695..e6b6535effb2 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -28,7 +28,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ) -var typesStandard = EIP712Types{ +var typesStandard = Types{ "EIP712Domain": { { "name": "name", @@ -75,7 +75,7 @@ var typesStandard = EIP712Types{ const primaryType = "Mail" -var domainStandard = EIP712Domain{ +var domainStandard = TypedDataDomain{ "Ether Mail", "1", big.NewInt(1), @@ -169,7 +169,7 @@ func TestHashStruct(t *testing.T) { } domainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(hash)) if domainHash != "0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f" { - t.Errorf("Expected different hashStruct result (got %s)", domainHash) + t.Errorf("Expected different domain hashStruct result (got %s)", domainHash) } } From e2e97eba50b14a09e6b0eb90c8d3782e94489248 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Tue, 20 Nov 2018 12:58:32 +0200 Subject: [PATCH 65/84] Added pseudo-failing test --- signer/core/signed_data_test.go | 154 +++++++++++++++++--------------- 1 file changed, 83 insertions(+), 71 deletions(-) diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index e6b6535effb2..80e93805455a 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -73,6 +73,78 @@ var typesStandard = Types{ }, } +var jsonTypedData = ` + { + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Person": [ + { + "name": "name", + "type": "string" + }, + { + "name": "test", + "type": "uint8" + }, + { + "name": "wallet", + "type": "address" + } + ], + "Mail": [ + { + "name": "from", + "type": "Person" + }, + { + "name": "to", + "type": "Person" + }, + { + "name": "contents", + "type": "string" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": 1, + "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "test": 3, + "wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } + } +` + const primaryType = "Mail" var domainStandard = TypedDataDomain{ @@ -500,82 +572,14 @@ func TestMalformedData4(t *testing.T) { //{ // "test": 65536 <-- test defined as uint8 //} - jsonTypedData := ` - { - "types": { - "EIP712Domain": [ - { - "name": "name", - "type": "string" - }, - { - "name": "version", - "type": "string" - }, - { - "name": "chainId", - "type": "uint256" - }, - { - "name": "verifyingContract", - "type": "address" - } - ], - "Person": [ - { - "name": "name", - "type": "string" - }, - { - "name": "test", - "type": "uint8" - }, - { - "name": "wallet", - "type": "address" - } - ], - "Mail": [ - { - "name": "from", - "type": "Person" - }, - { - "name": "to", - "type": "Person" - }, - { - "name": "contents", - "type": "string" - } - ] - }, - "primaryType": "Mail", - "domain": { - "name": "Ether Mail", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "from": { - "name": "Cow", - "test": 65536, - "wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to": { - "name": "Bob", - "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents": "Hello, Bob!" - } - } -` var malformedTypedData TypedData err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) if err != nil { t.Fatalf("unmarshalling failed %v", err) } + // Set test to something outside uint8 + (malformedTypedData.Message["from"]).(map[string]interface{})["test"] = 65536 + err = malformedTypedData.Validate() if err != nil { t.Fatalf("Expected no error, got %v", err) @@ -585,4 +589,12 @@ func TestMalformedData4(t *testing.T) { if err == nil || err.Error() != "provided data '65536' doesn't match type 'uint8'" { t.Fatalf("Expected `provided data '65536' doesn't match type 'uint8'`, got %v", err) } + + // Set it to something that should work + (malformedTypedData.Message["from"]).(map[string]interface{})["test"] = 3 + + _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) + if err != nil { + t.Fatalf("Expected no err, got %v", err) + } } From 457e86cd9384b58533d14dcdaf05121bfe735d61 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Wed, 21 Nov 2018 15:03:42 +0200 Subject: [PATCH 66/84] Fixed false positive test --- signer/core/signed_data_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 80e93805455a..6761231c527d 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -590,8 +590,8 @@ func TestMalformedData4(t *testing.T) { t.Fatalf("Expected `provided data '65536' doesn't match type 'uint8'`, got %v", err) } - // Set it to something that should work - (malformedTypedData.Message["from"]).(map[string]interface{})["test"] = 3 + (malformedTypedData.Message["from"]).(map[string]interface{})["test"] = big.NewInt(3) + (malformedTypedData.Message["to"]).(map[string]interface{})["test"] = big.NewInt(4) _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) if err != nil { From 2763dd41d6828347807bae22ee3ece2c6a0a61ef Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Tue, 27 Nov 2018 01:07:00 +0200 Subject: [PATCH 67/84] Added PrettyPrint method --- signer/core/signed_data.go | 122 +++++++++++++++++++++++++++++-------- 1 file changed, 96 insertions(+), 26 deletions(-) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 2d26ec80493e..39e78ea46b97 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -74,7 +74,6 @@ type TypedData struct { PrimaryType string `json:"primaryType"` Domain TypedDataDomain `json:"domain"` Message TypedDataMessage `json:"message"` - Output bytes.Buffer } type Type []map[string]string @@ -266,16 +265,13 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd if err != nil { return nil, err } - typedData.Output.Reset() - typedData.Output.WriteString(fmt.Sprintf("%s {\n", typedData.PrimaryType)) typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) if err != nil { return nil, err } - typedData.Output.Truncate(typedData.Output.Len() - 2) - typedData.Output.WriteString(fmt.Sprintf("\n}")) sighash := crypto.Keccak256([]byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))) - req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: typedData.Map(), Message: typedData.Output.String(), Hash: sighash} + output := typedData.PrettyPrint() + req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: typedData.Map(), Message: output, Hash: sighash} signature, err := api.Sign(ctx, addr, req) if err != nil { api.UI.ShowError(err.Error()) @@ -377,8 +373,6 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter if !ok { return nil, dataMismatchError(encType, encValue) } - typedData.Output.WriteString(strings.Repeat("\u00a0", depth*2)) - typedData.Output.WriteString(fmt.Sprintf("\"%s\": [ %s\n", field["name"], encType)) arrayBuffer := bytes.Buffer{} parsedType := strings.Split(encType, "[")[0] @@ -394,7 +388,7 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter } arrayBuffer.Write(encodedData) } else { - encValue, err := typedData.HandlePrimitiveValue(encType, encValue, depth) + encValue, err := typedData.EncodePrimitiveValue(encType, encValue, depth) if err != nil { return nil, err } @@ -406,27 +400,21 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter } } - typedData.Output.Truncate(typedData.Output.Len() - 2) - typedData.Output.WriteString(fmt.Sprintf("\n%s],\n", strings.Repeat("\u00a0", depth*2))) buffer.Write(crypto.Keccak256(arrayBuffer.Bytes())) } else if typedData.Types[field["type"]] != nil { mapValue, ok := encValue.(map[string]interface{}) if !ok { return nil, dataMismatchError(encType, encValue) } - typedData.Output.WriteString(strings.Repeat("\u00a0", depth*2)) - typedData.Output.WriteString(fmt.Sprintf("\"%s\": { %s\n", field["name"], encType)) encodedData, err := typedData.EncodeData(field["type"], mapValue, depth+1) if err != nil { return nil, err } - typedData.Output.Truncate(typedData.Output.Len() - 2) - typedData.Output.WriteString(fmt.Sprintf("\n%s},\n", strings.Repeat("\u00a0", depth*2))) buffer.Write(crypto.Keccak256(encodedData)) } else { - primitiveEncValue, err := typedData.HandlePrimitiveValue(encType, encValue, depth) + primitiveEncValue, err := typedData.EncodePrimitiveValue(encType, encValue, depth) if err != nil { return nil, err } @@ -438,16 +426,14 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter } } - return buffer.Bytes(), nil // https://github.com/ethereumjs/ethereumjs-abi/blob/master/lib/index.js#L336 + return buffer.Bytes(), nil } -// handlePrimitiveValues deals with the primitive values found +// EncodePrimitiveValue deals with the primitive values found // while searching through the typed data -func (typedData *TypedData) HandlePrimitiveValue(encType string, encValue interface{}, depth int) (interface{}, error) { +func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interface{}, depth int) (interface{}, error) { var primitiveEncValue interface{} - typedData.Output.WriteString(strings.Repeat("\u00a0", depth*2)) - typedData.Output.WriteString(fmt.Sprintf("\"%s\": ", encType)) switch encType { case "address": bytesValue := hexutil.Bytes{} @@ -463,7 +449,6 @@ func (typedData *TypedData) HandlePrimitiveValue(encType string, encValue interf bytesValue = append(bytesValue, _byte) } primitiveEncValue = bytesValue - typedData.Output.WriteString(fmt.Sprintf("%s,\n", addressValue.String())) case "bool": var int64Val int64 boolValue, ok := encValue.(bool) @@ -474,14 +459,12 @@ func (typedData *TypedData) HandlePrimitiveValue(encType string, encValue interf int64Val = 1 } primitiveEncValue = abi.U256(big.NewInt(int64Val)) - typedData.Output.WriteString(fmt.Sprintf("%t,\n", boolValue)) case "bytes", "string": bytesValue, err := bytesValueOf(encValue) if err != nil { return nil, dataMismatchError(encType, encValue) } primitiveEncValue = crypto.Keccak256(bytesValue) - typedData.Output.WriteString(fmt.Sprintf("\"%s\",\n", encValue)) default: if strings.HasPrefix(encType, "bytes") { sizeStr := strings.TrimPrefix(encType, "bytes") @@ -495,14 +478,12 @@ func (typedData *TypedData) HandlePrimitiveValue(encType string, encValue interf } bytesValue = append(bytesValue, encValue.(hexutil.Bytes)...) primitiveEncValue = bytesValue - typedData.Output.WriteString(fmt.Sprintf("\"%s\",\n", encValue)) } else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { bigIntValue, ok := encValue.(*big.Int) if !ok { return nil, dataMismatchError(encType, encValue) } primitiveEncValue = abi.U256(bigIntValue) - typedData.Output.WriteString(fmt.Sprintf("%d,\n", bigIntValue)) } else { return nil, fmt.Errorf("unrecognized type '%s'", encType) } @@ -624,6 +605,95 @@ func (typedData *TypedData) Map() map[string]interface{} { return dataMap } +// PrettyPrint generates a nice output to help the users +// of clef present data in their apps +func (typedData *TypedData) PrettyPrint() string { + output := bytes.Buffer{} + + output.WriteString(fmt.Sprintf("%s {\n", "Domain")) + output.WriteString(typedData.PrettyPrintData("EIP712Domain", typedData.Domain.Map(), 1)) + output.Truncate(output.Len() - 2) + output.WriteString(fmt.Sprintf("\n}\n")) + + output.WriteString(fmt.Sprintf("%s {\n", typedData.PrimaryType)) + output.WriteString(typedData.PrettyPrintData(typedData.PrimaryType, typedData.Message, 1)) + output.Truncate(output.Len() - 2) + output.WriteString(fmt.Sprintf("\n}")) + + return output.String() +} + +// PrettyPrintData generates a formatted output for the +// given data +func (typedData *TypedData) PrettyPrintData(primaryType string, data map[string]interface{}, depth int) string { + output := bytes.Buffer{} + + // Add field contents. Structs and arrays have special handlers. + for _, field := range typedData.Types[primaryType] { + encType := field["type"] + encName := field["name"] + encValue := data[encName] + + if encType[len(encType)-1:] == "]" { + arrayValue, _ := encValue.([]interface{}) + parsedType := strings.Split(encType, "[")[0] + for _, item := range arrayValue { + if typedData.Types[parsedType] != nil { + mapValue, _ := item.(map[string]interface{}) + mapOutput := typedData.PrettyPrintData(parsedType, mapValue, depth+1) + output.WriteString(mapOutput) + } else { + primitiveOutput := typedData.PrettyPrintPrimitiveValue(encType, encName, encValue, depth) + output.WriteString(primitiveOutput) + } + } + } else if typedData.Types[field["type"]] != nil { + output.WriteString(strings.Repeat("\u00a0", depth*2)) + output.WriteString(fmt.Sprintf("\"%s\": { %s\n", field["name"], encType)) + + mapValue, _ := encValue.(map[string]interface{}) + mapOutput := typedData.PrettyPrintData(field["type"], mapValue, depth+1) + output.WriteString(mapOutput) + + output.Truncate(output.Len() - 2) + output.WriteString(fmt.Sprintf("\n%s},\n", strings.Repeat("\u00a0", depth*2))) + } else { + primitiveOutput := typedData.PrettyPrintPrimitiveValue(encType, encName, encValue, depth) + output.WriteString(primitiveOutput) + } + } + + return output.String() +} + +// PrettyPrintPrimitiveValue generates a formatted output for the +// given primitive value +func (typedData *TypedData) PrettyPrintPrimitiveValue(encType string, encName string, encValue interface{}, depth int) string { + output := bytes.Buffer{} + output.WriteString(strings.Repeat("\u00a0", depth*2)) + output.WriteString(fmt.Sprintf("\"%s\": ", encName)) + + switch encType { + case "address": + stringValue, _ := encValue.(string) + addressValue := common.HexToAddress(stringValue) + output.WriteString(fmt.Sprintf("%s,\n", addressValue.String())) + case "bool": + boolValue, _ := encValue.(bool) + output.WriteString(fmt.Sprintf("%t,\n", boolValue)) + case "bytes", "string": + output.WriteString(fmt.Sprintf("\"%s\",\n", encValue)) + default: + if strings.HasPrefix(encType, "bytes") { + output.WriteString(fmt.Sprintf("\"%s\",\n", encValue)) + } else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { + bigIntValue, _ := encValue.(*big.Int) + output.WriteString(fmt.Sprintf("%d,\n", bigIntValue)) + } + } + return output.String() +} + // Validate checks if the types object is conformant to the specs func (types *Types) Validate() error { for typeKey, typeArr := range *types { From 5f45cdc9fb27712bf6a409c792be896263682c8c Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 3 Dec 2018 13:26:00 +0100 Subject: [PATCH 68/84] signer: refactor formatting and UI --- signer/core/signed_data.go | 125 +++++++++++++++++++------------- signer/core/signed_data_test.go | 30 ++++++++ 2 files changed, 103 insertions(+), 52 deletions(-) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 39e78ea46b97..e5ba64eab251 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -425,7 +425,6 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter buffer.Write(bytesValue) } } - return buffer.Bytes(), nil } @@ -609,87 +608,109 @@ func (typedData *TypedData) Map() map[string]interface{} { // of clef present data in their apps func (typedData *TypedData) PrettyPrint() string { output := bytes.Buffer{} - - output.WriteString(fmt.Sprintf("%s {\n", "Domain")) - output.WriteString(typedData.PrettyPrintData("EIP712Domain", typedData.Domain.Map(), 1)) - output.Truncate(output.Len() - 2) - output.WriteString(fmt.Sprintf("\n}\n")) - - output.WriteString(fmt.Sprintf("%s {\n", typedData.PrimaryType)) - output.WriteString(typedData.PrettyPrintData(typedData.PrimaryType, typedData.Message, 1)) - output.Truncate(output.Len() - 2) - output.WriteString(fmt.Sprintf("\n}")) - + formatted := typedData.Format() + for _, item := range formatted { + output.WriteString(fmt.Sprintf("%v\n", item.Pprint(0))) + } return output.String() } -// PrettyPrintData generates a formatted output for the -// given data -func (typedData *TypedData) PrettyPrintData(primaryType string, data map[string]interface{}, depth int) string { - output := bytes.Buffer{} +// Format returns a representation of d, which can be easily displayed by a user-interface +// without in-depth knowledge about 712 rules +func (typedData *TypedData) Format() []*NameValueType { + var nvts []*NameValueType + nvts = append(nvts, &NameValueType{ + Name: "EIP712Domain", + Value: typedData.formatData("EIP712Domain", typedData.Domain.Map()), + Typ: "domain", + }) + nvts = append(nvts, &NameValueType{ + Name: typedData.PrimaryType, + Value: typedData.formatData(typedData.PrimaryType, typedData.Message), + Typ: "primary type", + }) + return nvts +} + +func (typedData *TypedData) formatData(primaryType string, data map[string]interface{}) []*NameValueType { + var output []*NameValueType // Add field contents. Structs and arrays have special handlers. for _, field := range typedData.Types[primaryType] { encType := field["type"] encName := field["name"] encValue := data[encName] - + item := &NameValueType{ + Name: encName, + Typ: encType, + } if encType[len(encType)-1:] == "]" { arrayValue, _ := encValue.([]interface{}) parsedType := strings.Split(encType, "[")[0] - for _, item := range arrayValue { + for _, v := range arrayValue { if typedData.Types[parsedType] != nil { - mapValue, _ := item.(map[string]interface{}) - mapOutput := typedData.PrettyPrintData(parsedType, mapValue, depth+1) - output.WriteString(mapOutput) + mapValue, _ := v.(map[string]interface{}) + mapOutput := typedData.formatData(parsedType, mapValue) + item.Value = mapOutput } else { - primitiveOutput := typedData.PrettyPrintPrimitiveValue(encType, encName, encValue, depth) - output.WriteString(primitiveOutput) + primitiveOutput := formatPrimitiveValue(encType, encValue) + item.Value = primitiveOutput } } } else if typedData.Types[field["type"]] != nil { - output.WriteString(strings.Repeat("\u00a0", depth*2)) - output.WriteString(fmt.Sprintf("\"%s\": { %s\n", field["name"], encType)) - mapValue, _ := encValue.(map[string]interface{}) - mapOutput := typedData.PrettyPrintData(field["type"], mapValue, depth+1) - output.WriteString(mapOutput) - - output.Truncate(output.Len() - 2) - output.WriteString(fmt.Sprintf("\n%s},\n", strings.Repeat("\u00a0", depth*2))) + mapOutput := typedData.formatData(field["type"], mapValue) + item.Value = mapOutput } else { - primitiveOutput := typedData.PrettyPrintPrimitiveValue(encType, encName, encValue, depth) - output.WriteString(primitiveOutput) + primitiveOutput := formatPrimitiveValue(encType, encValue) + item.Value = primitiveOutput } + output = append(output, item) } - - return output.String() + return output } -// PrettyPrintPrimitiveValue generates a formatted output for the -// given primitive value -func (typedData *TypedData) PrettyPrintPrimitiveValue(encType string, encName string, encValue interface{}, depth int) string { - output := bytes.Buffer{} - output.WriteString(strings.Repeat("\u00a0", depth*2)) - output.WriteString(fmt.Sprintf("\"%s\": ", encName)) - +func formatPrimitiveValue(encType string, encValue interface{}) string { switch encType { case "address": stringValue, _ := encValue.(string) - addressValue := common.HexToAddress(stringValue) - output.WriteString(fmt.Sprintf("%s,\n", addressValue.String())) + return common.HexToAddress(stringValue).String() case "bool": boolValue, _ := encValue.(bool) - output.WriteString(fmt.Sprintf("%t,\n", boolValue)) + return fmt.Sprintf("%t", boolValue) case "bytes", "string": - output.WriteString(fmt.Sprintf("\"%s\",\n", encValue)) - default: - if strings.HasPrefix(encType, "bytes") { - output.WriteString(fmt.Sprintf("\"%s\",\n", encValue)) - } else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { - bigIntValue, _ := encValue.(*big.Int) - output.WriteString(fmt.Sprintf("%d,\n", bigIntValue)) + return fmt.Sprintf("%s", encValue) + } + if strings.HasPrefix(encType, "bytes") { + return fmt.Sprintf("%s", encValue) + } else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { + bigIntValue, _ := encValue.(*big.Int) + return fmt.Sprintf("%d (0x%x)", bigIntValue, bigIntValue) + } + return "NA" +} + +// NameValueType is a very simple struct with Name, Value and Type. It's meant for simple +// json structures used to communicate signing-info about typed data with the UI +type NameValueType struct { + Name string `json:"name"` + Value interface{} `json:"value"` + Typ string `json:"type"` +} + +// Pprint returns a pretty-printed version of nvt +func (nvt *NameValueType) Pprint(depth int) string { + output := bytes.Buffer{} + output.WriteString(strings.Repeat("\u00a0", depth*2)) + output.WriteString(fmt.Sprintf("%s [%s]: ", nvt.Name, nvt.Typ)) + if nvts, ok := nvt.Value.([]*NameValueType); ok { + output.WriteString("\n") + for _, next := range nvts { + sublevel := next.Pprint(depth + 1) + output.WriteString(sublevel) } + } else { + output.WriteString(fmt.Sprintf("%s\n", nvt.Value)) } return output.String() } diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 6761231c527d..26221b806821 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -598,3 +598,33 @@ func TestMalformedData4(t *testing.T) { t.Fatalf("Expected no err, got %v", err) } } + +func TestFormatter(t *testing.T) { + + var d TypedData + err := json.Unmarshal([]byte(jsonTypedData), &d) + if err != nil { + t.Fatalf("unmarshalling failed %v", err) + } + + //x := PrettyPrintPrimitiveValue("address", "wallet", "0x123123123", 1) + //fmt.Printf(x) + //y := FormatPrimitiveValue("address", "wallet", "0x123123123") + //fmt.Printf("%v\n", y) + + //fmt.Printf(d.PrettyPrintData(d.PrimaryType,d.Message, 1)) + + //formatted := d.FormatData(d.PrimaryType,d.Message) + //for _,item := range formatted{ + // fmt.Printf("%v\n", item.Pprint(0)) + //} + + formatted := d.Format() + for _, item := range formatted { + fmt.Printf("%v\n", item.Pprint(0)) + } + + j, _ := json.Marshal(formatted) + fmt.Printf("%v\n", string(j)) + +} From c182e18fb3805cb824f88c1643ef48eb9d2426ae Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 3 Dec 2018 13:46:46 +0100 Subject: [PATCH 69/84] signer: make ui use new message format for signing --- signer/core/api.go | 7 ++++--- signer/core/cliui.go | 6 +++++- signer/core/signed_data.go | 34 ++++++++++++++++++++++++++++------ 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/signer/core/api.go b/signer/core/api.go index 49532f6ac43b..16968f7ad872 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -176,9 +176,10 @@ type ( ContentType string `json:"content_type"` Address common.MixedcaseAddress `json:"address"` Rawdata interface{} `json:"raw_data"` - Message string `json:"message"` - Hash hexutil.Bytes `json:"hash"` - Meta Metadata `json:"meta"` + //Message string `json:"message"` + Message []*NameValueType `json:"message"` + Hash hexutil.Bytes `json:"hash"` + Meta Metadata `json:"meta"` } SignDataResponse struct { Approved bool `json:"approved"` diff --git a/signer/core/cliui.go b/signer/core/cliui.go index 035d4f201680..9c5250c4efb2 100644 --- a/signer/core/cliui.go +++ b/signer/core/cliui.go @@ -164,7 +164,11 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResp fmt.Printf("-------- Sign data request--------------\n") fmt.Printf("Account: %s\n", request.Address.String()) - fmt.Printf("message: \n%v\n", request.Message) + fmt.Printf("message:\n") + for _, nvt := range request.Message { + fmt.Printf("%v\n", nvt.Pprint(1)) + } + //fmt.Printf("message: \n%v\n", request.Message) fmt.Printf("raw data: \n%v\n", request.Rawdata) fmt.Printf("message hash: %v\n", request.Hash) fmt.Printf("-------------------------------------------\n") diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index e5ba64eab251..bcf77e465011 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -168,7 +168,14 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M return nil, err } sighash, msg := SignTextValidator(validatorData) - req = &SignDataRequest{ContentType: mediaType, Rawdata: validatorData, Message: msg, Hash: sighash} + message := []*NameValueType{ + &NameValueType{ + Name: "message", + Typ: "text", + Value: msg, + }, + } + req = &SignDataRequest{ContentType: mediaType, Rawdata: validatorData, Message: message, Hash: sighash} case ApplicationClique.Mime: // Clique is the Ethereum PoA standard cliqueData, err := hexutil.Decode(data.(string)) @@ -183,8 +190,14 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M if err != nil { return nil, err } - msg := fmt.Sprintf("clique block %d [0x%x]", header.Number, header.Hash()) - req = &SignDataRequest{ContentType: mediaType, Rawdata: cliqueData, Message: msg, Hash: sighash} + message := []*NameValueType{ + &NameValueType{ + Name: "Clique block", + Typ: "clique", + Value: fmt.Sprintf("clique block %d [0x%x]", header.Number, header.Hash()), + }, + } + req = &SignDataRequest{ContentType: mediaType, Rawdata: cliqueData, Message: message, Hash: sighash} case TextPlain.Mime: // Calculates an Ethereum ECDSA signature for: // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") @@ -193,7 +206,15 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M return nil, err } sighash, msg := SignTextPlain(plainData) - req = &SignDataRequest{ContentType: mediaType, Rawdata: plainData, Message: msg, Hash: sighash} + message := []*NameValueType{ + &NameValueType{ + Name: "message", + Typ: "text/plain", + Value: msg, + }, + } + + req = &SignDataRequest{ContentType: mediaType, Rawdata: plainData, Message: message, Hash: sighash} default: return nil, fmt.Errorf("content type '%s' not implemented for signing", contentType) } @@ -270,8 +291,9 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd return nil, err } sighash := crypto.Keccak256([]byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))) - output := typedData.PrettyPrint() - req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: typedData.Map(), Message: output, Hash: sighash} + //output := typedData.PrettyPrint() + message := typedData.Format() + req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: typedData.Map(), Message: message, Hash: sighash} signature, err := api.Sign(ctx, addr, req) if err != nil { api.UI.ShowError(err.Error()) From 39317d03b42933aa722d8832554ffa04477cd07c Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Tue, 4 Dec 2018 00:42:52 +0200 Subject: [PATCH 70/84] Fixed breaking changes --- signer/core/signed_data.go | 6 +++--- signer/rules/rules_test.go | 10 +++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index bcf77e465011..42b2911d4af5 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -169,7 +169,7 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M } sighash, msg := SignTextValidator(validatorData) message := []*NameValueType{ - &NameValueType{ + { Name: "message", Typ: "text", Value: msg, @@ -191,7 +191,7 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M return nil, err } message := []*NameValueType{ - &NameValueType{ + { Name: "Clique block", Typ: "clique", Value: fmt.Sprintf("clique block %d [0x%x]", header.Number, header.Hash()), @@ -207,7 +207,7 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M } sighash, msg := SignTextPlain(plainData) message := []*NameValueType{ - &NameValueType{ + { Name: "message", Typ: "text/plain", Value: msg, diff --git a/signer/rules/rules_test.go b/signer/rules/rules_test.go index 7f5bf71ad5a4..bf444218485f 100644 --- a/signer/rules/rules_test.go +++ b/signer/rules/rules_test.go @@ -642,9 +642,17 @@ function ApproveSignData(r){ addr, _ := mixAddr("0x694267f14675d7e1b9494fd8d72fefe1755710fa") fmt.Printf("address %v %v\n", addr.String(), addr.Original()) + + nvt := []*core.NameValueType{ + { + Name: "message", + Typ: "text/plain", + Value: msg, + }, + } resp, err := r.ApproveSignData(&core.SignDataRequest{ Address: *addr, - Message: msg, + Message: nvt, Hash: hash, Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"}, Rawdata: raw, From c096d738c777ea373e8edde96c7edf86b513ac0e Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Tue, 4 Dec 2018 11:36:03 +0200 Subject: [PATCH 71/84] Fixed rules_test failing test --- signer/rules/rules_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/signer/rules/rules_test.go b/signer/rules/rules_test.go index bf444218485f..2f5b0d593a90 100644 --- a/signer/rules/rules_test.go +++ b/signer/rules/rules_test.go @@ -624,7 +624,7 @@ func TestSignData(t *testing.T) { function ApproveSignData(r){ if( r.address.toLowerCase() == "0x694267f14675d7e1b9494fd8d72fefe1755710fa") { - if(r.message.indexOf("bazonk") >= 0){ + if(r.message[0].value.indexOf("bazonk") >= 0){ return "Approve" } return "Reject" @@ -636,8 +636,8 @@ function ApproveSignData(r){ t.Errorf("Couldn't create evaluator %v", err) return } - message := []byte("baz bazonk foo") - hash, msg := core.SignTextPlain(message) + message := "baz bazonk foo" + hash, _ := core.SignTextPlain([]byte(message)) raw := hexutil.Bytes(message) addr, _ := mixAddr("0x694267f14675d7e1b9494fd8d72fefe1755710fa") @@ -647,7 +647,7 @@ function ApproveSignData(r){ { Name: "message", Typ: "text/plain", - Value: msg, + Value: message, }, } resp, err := r.ApproveSignData(&core.SignDataRequest{ From 33a1ac635cd00ba25eda16cf7194b305d30157ef Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Thu, 6 Dec 2018 08:51:12 +0000 Subject: [PATCH 72/84] Added extra regexp for reference types --- signer/core/signed_data.go | 13 +++++++++---- signer/core/signed_data_test.go | 8 ++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 42b2911d4af5..9601a39e77e8 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -96,6 +96,7 @@ type TypedDataDomain struct { } var typedDataRegexp = regexp.MustCompile(`^((address|bool|bytes|string)|((bytes)([1-9]|[1-2][0-9]|3[0-2]))|((int|uint)(8|16|32|64|128|256)))(\[])?$`) +var typedDataReferenceTypeRegexp = regexp.MustCompile(`^[A-Z](\w*)(\[])?$`) // Sign receives a request and produces a signature @@ -747,15 +748,19 @@ func (types *Types) Validate() error { } firstChar := []rune(typeVal)[0] if unicode.IsUpper(firstChar) { - if (*types)[typeVal] == nil { - return fmt.Errorf("referenced type '%s' is undefined", typeVal) + if (*types)[typeVal] != nil { + if !typedDataReferenceTypeRegexp.MatchString(typeVal) { + return fmt.Errorf("unknown reference type '%s", typeVal) + } + } else { + return fmt.Errorf("reference type '%s' is undefined", typeVal) } } else { if !typedDataRegexp.MatchString(typeVal) { if (*types)[typeVal] != nil { - return fmt.Errorf("referenced type '%s' must be capitalized", typeVal) + return fmt.Errorf("reference type '%s' must be capitalized", typeVal) } else { - return fmt.Errorf("unknown atomic type '%s'", typeVal) + return fmt.Errorf("unknown type '%s'", typeVal) } } } diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 26221b806821..7a8df321cf76 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -463,8 +463,8 @@ func TestMalformedData2(t *testing.T) { malformedTypedData.Types["Mail"][2]["type"] = "Blahonga" err = malformedTypedData.Validate() - if err == nil || err.Error() != "referenced type 'Blahonga' is undefined" { - t.Fatalf("Expected `referenced type 'Blahonga' is undefined`, got %v", err) + if err == nil || err.Error() != "reference type 'Blahonga' is undefined" { + t.Fatalf("Expected `reference type 'Blahonga' is undefined`, got %v", err) } _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) if err == nil || err.Error() != "unrecognized type 'Blahonga'" { @@ -555,8 +555,8 @@ func TestMalformedData3(t *testing.T) { t.Fatalf("unmarshalling failed %v", err) } err = malformedTypedData.Validate() - if err == nil || err.Error() != "unknown atomic type 'uint256 ... and now for something completely different'" { - t.Fatalf("Expected `unknown atomic type 'uint256 ... and now for something completely different'`, got %v", err) + if err == nil || err.Error() != "unknown type 'uint256 ... and now for something completely different'" { + t.Fatalf("Expected `unknown type 'uint256 ... and now for something completely different'`, got %v", err) } malformedTypedData.Types["EIP712Domain"][2]["type"] = "uint256" From 6b30617820d42004373d6f4e8ffeeddfbcc81426 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 6 Dec 2018 14:08:47 +0100 Subject: [PATCH 73/84] signer: more hard types --- signer/core/signed_data.go | 206 ++++++++++++++------------------ signer/core/signed_data_test.go | 125 +++++++++++++++---- 2 files changed, 198 insertions(+), 133 deletions(-) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 9601a39e77e8..ff0ad157d9db 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -21,6 +21,7 @@ import ( "context" "errors" "fmt" + "github.com/ethereum/go-ethereum/common/math" "math/big" "mime" "reflect" @@ -76,9 +77,30 @@ type TypedData struct { Message TypedDataMessage `json:"message"` } -type Type []map[string]string +type Type struct { + Name string `json:"name"` + Type string `json:"type"` +} + +func (t *Type) isArray() bool { + return strings.HasSuffix(t.Type, "[]") +} + +// typeName returns the canonical name of the type. If the type is 'Person[]', then +// this method returns 'Person' +func (t *Type) typeName() string { + if strings.HasSuffix(t.Type, "[]") { + return strings.TrimSuffix(t.Type, "[]") + } + return t.Type +} + +func (t *Type) isReferenceType() bool { + // Reference types must have a leading uppercase characer + return unicode.IsUpper([]rune(t.Type)[0]) +} -type Types map[string]Type +type Types map[string][]Type type TypePriority struct { Type string @@ -331,7 +353,7 @@ func (typedData *TypedData) Dependencies(primaryType string, found []string) []s } found = append(found, primaryType) for _, field := range typedData.Types[primaryType] { - for _, dep := range typedData.Dependencies(field["type"], found) { + for _, dep := range typedData.Dependencies(field.Type, found) { if !includes(found, dep) { found = append(found, dep) } @@ -357,9 +379,9 @@ func (typedData *TypedData) EncodeType(primaryType string) hexutil.Bytes { buffer.WriteString(dep) buffer.WriteString("(") for _, obj := range typedData.Types[dep] { - buffer.WriteString(obj["type"]) + buffer.WriteString(obj.Type) buffer.WriteString(" ") - buffer.WriteString(obj["name"]) + buffer.WriteString(obj.Name) buffer.WriteString(",") } buffer.Truncate(buffer.Len() - 1) @@ -389,8 +411,8 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter // Add field contents. Structs and arrays have special handlers. for _, field := range typedData.Types[primaryType] { - encType := field["type"] - encValue := data[field["name"]] + encType := field.Type + encValue := data[field.Name] if encType[len(encType)-1:] == "]" { arrayValue, ok := encValue.([]interface{}) if !ok { @@ -411,11 +433,7 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter } arrayBuffer.Write(encodedData) } else { - encValue, err := typedData.EncodePrimitiveValue(encType, encValue, depth) - if err != nil { - return nil, err - } - bytesValue, err := bytesValueOf(encValue) + bytesValue, err := typedData.EncodePrimitiveValue(encType, encValue, depth) if err != nil { return nil, err } @@ -424,28 +442,22 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter } buffer.Write(crypto.Keccak256(arrayBuffer.Bytes())) - } else if typedData.Types[field["type"]] != nil { + } else if typedData.Types[field.Type] != nil { mapValue, ok := encValue.(map[string]interface{}) if !ok { return nil, dataMismatchError(encType, encValue) } - - encodedData, err := typedData.EncodeData(field["type"], mapValue, depth+1) + encodedData, err := typedData.EncodeData(field.Type, mapValue, depth+1) if err != nil { return nil, err } - buffer.Write(crypto.Keccak256(encodedData)) } else { - primitiveEncValue, err := typedData.EncodePrimitiveValue(encType, encValue, depth) - if err != nil { - return nil, err - } - bytesValue, err := bytesValueOf(primitiveEncValue) + byteValue, err := typedData.EncodePrimitiveValue(encType, encValue, depth) if err != nil { return nil, err } - buffer.Write(bytesValue) + buffer.Write(byteValue) } } return buffer.Bytes(), nil @@ -453,64 +465,64 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter // EncodePrimitiveValue deals with the primitive values found // while searching through the typed data -func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interface{}, depth int) (interface{}, error) { - var primitiveEncValue interface{} +func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interface{}, depth int) ([]byte, error) { switch encType { case "address": - bytesValue := hexutil.Bytes{} - for i := 0; i < 12; i++ { - bytesValue = append(bytesValue, 0) - } stringValue, ok := encValue.(string) if !ok || !common.IsHexAddress(stringValue) { return nil, dataMismatchError(encType, encValue) } - addressValue := common.HexToAddress(stringValue) - for _, _byte := range addressValue { - bytesValue = append(bytesValue, _byte) - } - primitiveEncValue = bytesValue + retval := make([]byte, 32) + copy(retval[12:], common.HexToAddress(stringValue).Bytes()) + return retval, nil case "bool": - var int64Val int64 boolValue, ok := encValue.(bool) if !ok { return nil, dataMismatchError(encType, encValue) } if boolValue { - int64Val = 1 + return math.PaddedBigBytes(common.Big1, 32), nil } - primitiveEncValue = abi.U256(big.NewInt(int64Val)) - case "bytes", "string": - bytesValue, err := bytesValueOf(encValue) - if err != nil { + return math.PaddedBigBytes(common.Big0, 32), nil + case "string": + strVal, ok := encValue.(string) + if !ok { return nil, dataMismatchError(encType, encValue) } - primitiveEncValue = crypto.Keccak256(bytesValue) - default: - if strings.HasPrefix(encType, "bytes") { - sizeStr := strings.TrimPrefix(encType, "bytes") - size, _ := strconv.Atoi(sizeStr) - bytesValue := hexutil.Bytes{} - for i := 0; i < 32-size; i++ { - bytesValue = append(bytesValue, 0) - } - if _, ok := encValue.(hexutil.Bytes); !ok { - return nil, dataMismatchError(encType, encValue) - } - bytesValue = append(bytesValue, encValue.(hexutil.Bytes)...) - primitiveEncValue = bytesValue - } else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { - bigIntValue, ok := encValue.(*big.Int) - if !ok { - return nil, dataMismatchError(encType, encValue) - } - primitiveEncValue = abi.U256(bigIntValue) + return crypto.Keccak256([]byte(strVal)), nil + case "bytes": + bytesValue, ok := encValue.([]byte) + if !ok { + return nil, dataMismatchError(encType, encValue) + } + return crypto.Keccak256(bytesValue), nil + } + // bytes32 etc + if strings.HasPrefix(encType, "bytes") { + sizeStr := strings.TrimPrefix(encType, "bytes") + size, err := strconv.Atoi(sizeStr) + if err != nil { + return nil, fmt.Errorf("invalid size on bytes: %v", sizeStr) + } + if size < 0 || size > 32 { + return nil, fmt.Errorf("invalid size on bytes: %d", size) + } + if byteval, ok := encValue.(hexutil.Bytes); !ok { + return nil, dataMismatchError(encType, encValue) } else { - return nil, fmt.Errorf("unrecognized type '%s'", encType) + return math.PaddedBigBytes(new(big.Int).SetBytes(byteval), 32), nil } } - return primitiveEncValue, nil + if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { + bigIntValue, ok := encValue.(*big.Int) + if !ok { + return nil, dataMismatchError(encType, encValue) + } + return abi.U256(bigIntValue), nil + } + return nil, fmt.Errorf("unrecognized type '%s'", encType) + } // dataMismatchError generates an error for a mismatch between @@ -519,29 +531,6 @@ func dataMismatchError(encType string, encValue interface{}) error { return fmt.Errorf("provided data '%v' doesn't match type '%s'", encValue, encType) } -// bytesValuesOf returns the bytes value of the given interface -func bytesValueOf(_interface interface{}) (hexutil.Bytes, error) { - bytesValue, ok := _interface.(hexutil.Bytes) - if ok { - return bytesValue, nil - } - - switch reflect.TypeOf(_interface) { - case reflect.TypeOf(hexutil.Bytes{}): - return _interface.(hexutil.Bytes), nil - case reflect.TypeOf([]byte{}): - return hexutil.Bytes(_interface.([]byte)), nil - case reflect.TypeOf([]uint8{}): - return _interface.([]uint8), nil - case reflect.TypeOf(string("")): - return hexutil.Bytes(_interface.(string)), nil - default: - break - } - - return nil, fmt.Errorf("unrecognized type '%T'", _interface) -} - // EcRecover recovers the address associated with the given sig. // Only compatible with `text/plain` func (api *SignerAPI) EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { @@ -660,32 +649,31 @@ func (typedData *TypedData) formatData(primaryType string, data map[string]inter // Add field contents. Structs and arrays have special handlers. for _, field := range typedData.Types[primaryType] { - encType := field["type"] - encName := field["name"] + encName := field.Name encValue := data[encName] item := &NameValueType{ Name: encName, - Typ: encType, + Typ: field.Type, } - if encType[len(encType)-1:] == "]" { + if field.isArray() { arrayValue, _ := encValue.([]interface{}) - parsedType := strings.Split(encType, "[")[0] + parsedType := field.typeName() for _, v := range arrayValue { if typedData.Types[parsedType] != nil { mapValue, _ := v.(map[string]interface{}) mapOutput := typedData.formatData(parsedType, mapValue) item.Value = mapOutput } else { - primitiveOutput := formatPrimitiveValue(encType, encValue) + primitiveOutput := formatPrimitiveValue(field.Type, encValue) item.Value = primitiveOutput } } - } else if typedData.Types[field["type"]] != nil { + } else if typedData.Types[field.Type] != nil { mapValue, _ := encValue.(map[string]interface{}) - mapOutput := typedData.formatData(field["type"], mapValue) + mapOutput := typedData.formatData(field.Type, mapValue) item.Value = mapOutput } else { - primitiveOutput := formatPrimitiveValue(encType, encValue) + primitiveOutput := formatPrimitiveValue(field.Type, encValue) item.Value = primitiveOutput } output = append(output, item) @@ -739,30 +727,22 @@ func (nvt *NameValueType) Pprint(depth int) string { } // Validate checks if the types object is conformant to the specs -func (types *Types) Validate() error { - for typeKey, typeArr := range *types { +func (t Types) Validate() error { + for typeKey, typeArr := range t { for _, typeObj := range typeArr { - typeVal := typeObj["type"] - if typeKey == typeVal { - return fmt.Errorf("type '%s' cannot reference itself", typeVal) + if typeKey == typeObj.Type { + return fmt.Errorf("type '%s' cannot reference itself", typeObj.Type) } - firstChar := []rune(typeVal)[0] - if unicode.IsUpper(firstChar) { - if (*types)[typeVal] != nil { - if !typedDataReferenceTypeRegexp.MatchString(typeVal) { - return fmt.Errorf("unknown reference type '%s", typeVal) - } - } else { - return fmt.Errorf("reference type '%s' is undefined", typeVal) + if typeObj.isReferenceType() { + if _, exist := t[typeObj.Type]; !exist { + return fmt.Errorf("reference type '%s' is undefined", typeObj.Type) } - } else { - if !typedDataRegexp.MatchString(typeVal) { - if (*types)[typeVal] != nil { - return fmt.Errorf("reference type '%s' must be capitalized", typeVal) - } else { - return fmt.Errorf("unknown type '%s'", typeVal) - } + if !typedDataReferenceTypeRegexp.MatchString(typeObj.Type) { + return fmt.Errorf("unknown reference type '%s", typeObj.Type) } + + } else if !typedDataRegexp.MatchString(typeObj.Type) { + return fmt.Errorf("unknown type '%s'", typeObj.Type) } } } diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 7a8df321cf76..48e5b06e1d63 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -31,44 +31,44 @@ import ( var typesStandard = Types{ "EIP712Domain": { { - "name": "name", - "type": "string", + Name: "name", + Type: "string", }, { - "name": "version", - "type": "string", + Name: "version", + Type: "string", }, { - "name": "chainId", - "type": "uint256", + Name: "chainId", + Type: "uint256", }, { - "name": "verifyingContract", - "type": "address", + Name: "verifyingContract", + Type: "address", }, }, "Person": { { - "name": "name", - "type": "string", + Name: "name", + Type: "string", }, { - "name": "wallet", - "type": "address", + Name: "wallet", + Type: "address", }, }, "Mail": { { - "name": "from", - "type": "Person", + Name: "from", + Type: "Person", }, { - "name": "to", - "type": "Person", + Name: "to", + Type: "Person", }, { - "name": "contents", - "type": "string", + Name: "contents", + Type: "string", }, }, } @@ -461,7 +461,7 @@ func TestMalformedData2(t *testing.T) { t.Errorf("Expected `provided data 'Hello, Bob!' doesn't match type 'Person'`, got %v", err) } - malformedTypedData.Types["Mail"][2]["type"] = "Blahonga" + malformedTypedData.Types["Mail"][2].Type = "Blahonga" err = malformedTypedData.Validate() if err == nil || err.Error() != "reference type 'Blahonga' is undefined" { t.Fatalf("Expected `reference type 'Blahonga' is undefined`, got %v", err) @@ -559,7 +559,7 @@ func TestMalformedData3(t *testing.T) { t.Fatalf("Expected `unknown type 'uint256 ... and now for something completely different'`, got %v", err) } - malformedTypedData.Types["EIP712Domain"][2]["type"] = "uint256" + malformedTypedData.Types["EIP712Domain"][2].Type = "uint256" malformedTypedData.Message["blahonga"] = "zonk bonk" _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) if err == nil || err.Error() != "there is extra data provided in the message" { @@ -628,3 +628,88 @@ func TestFormatter(t *testing.T) { fmt.Printf("%v\n", string(j)) } + +func TestMalformedData5(t *testing.T) { + var jsonTypedData = ` + { + "types": { + "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" + } + ], + "Person[]": [ + { + "name": "baz", + "type": "string" + }], + "Mail": [ + { + "name": "from", + "type": "Person" + }, + { + "name": "to", + "type": "Person[]" + }, + { + "name": "contents", + "type": "string" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": 1, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": {"baz": "foo"}, + "contents": "Hello, Bob!" + } + } + +` + var malformedTypedData TypedData + err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) + if err != nil { + t.Fatalf("unmarshalling failed %v", err) + } + err = malformedTypedData.Validate() + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + _, err = malformedTypedData.HashStruct("EIP712Domain", malformedTypedData.Domain.Map()) + if err == nil { + t.Errorf("Expected an error, got %v", err) + } +} From d70a98d4b4f1710d37476b7e1d1016a1b640a94c Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Fri, 7 Dec 2018 17:08:40 +0000 Subject: [PATCH 74/84] Fixed failing test, formatted files --- signer/core/signed_data.go | 8 ++++---- signer/core/signed_data_test.go | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index ff0ad157d9db..8e7e97ea73bc 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -21,16 +21,16 @@ import ( "context" "errors" "fmt" - "github.com/ethereum/go-ethereum/common/math" "math/big" "mime" - "reflect" "regexp" "sort" "strconv" "strings" "unicode" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -508,10 +508,10 @@ func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interf if size < 0 || size > 32 { return nil, fmt.Errorf("invalid size on bytes: %d", size) } - if byteval, ok := encValue.(hexutil.Bytes); !ok { + if byteValue, ok := encValue.(hexutil.Bytes); !ok { return nil, dataMismatchError(encType, encValue) } else { - return math.PaddedBigBytes(new(big.Int).SetBytes(byteval), 32), nil + return math.PaddedBigBytes(new(big.Int).SetBytes(byteValue), 32), nil } } if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 48e5b06e1d63..b424f05f0b22 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -683,7 +683,7 @@ func TestMalformedData5(t *testing.T) { }, "primaryType": "Mail", "domain": { - "name": "Ether Mail", + "name": "Ether Mail", "version": "1", "chainId": 1, "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" @@ -709,7 +709,7 @@ func TestMalformedData5(t *testing.T) { t.Fatalf("Expected no error, got %v", err) } _, err = malformedTypedData.HashStruct("EIP712Domain", malformedTypedData.Domain.Map()) - if err == nil { - t.Errorf("Expected an error, got %v", err) + if err != nil { + t.Errorf("Expected no error, got %v", err) } } From c4f238cd922fe18d134724cddba33ccb4a2a992b Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 10 Jan 2019 13:12:00 +0100 Subject: [PATCH 75/84] signer: use golang/x keccak --- signer/core/signed_data.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 8e7e97ea73bc..e457da07dada 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -21,6 +21,7 @@ import ( "context" "errors" "fmt" + "golang.org/x/crypto/sha3" "math/big" "mime" "regexp" @@ -37,7 +38,6 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/crypto/sha3" "github.com/ethereum/go-ethereum/rlp" ) @@ -266,7 +266,7 @@ func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { if len(header.Extra) < 65 { return hash.Bytes(), fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) } - hasher := sha3.NewKeccak256() + hasher := sha3.NewLegacyKeccak256() rlp.Encode(hasher, []interface{}{ header.ParentHash, header.UncleHash, From 1e8f19db0aa212d8082872ef416318104ec8974b Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Fri, 11 Jan 2019 11:48:38 +0200 Subject: [PATCH 76/84] Fixed goimports error --- signer/core/signed_data.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index e457da07dada..3f8f7da5ac75 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -21,7 +21,6 @@ import ( "context" "errors" "fmt" - "golang.org/x/crypto/sha3" "math/big" "mime" "regexp" @@ -30,6 +29,8 @@ import ( "strings" "unicode" + "golang.org/x/crypto/sha3" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/accounts" From b0e872cd21ad87f06ecf04b57772102689e8273b Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 15 Jan 2019 13:32:44 +0100 Subject: [PATCH 77/84] clef, signer: address some review concerns --- cmd/clef/README.md | 5 +---- cmd/clef/extapi_changelog.md | 1 - signer/core/api.go | 1 - signer/core/signed_data.go | 16 ++++++---------- signer/core/signed_data_test.go | 23 +++++------------------ 5 files changed, 12 insertions(+), 34 deletions(-) diff --git a/cmd/clef/README.md b/cmd/clef/README.md index 5e6f8661c798..98e85d5a1159 100644 --- a/cmd/clef/README.md +++ b/cmd/clef/README.md @@ -489,11 +489,8 @@ Response ### account_ecRecover #### Sign data -<<<<<<< HEAD -======= ->>>>>>> c72099670... Added example RPC calls for account_signData and account_signTypedData - Derive the address from the account that was used to sign data with content type `text/plain` and the signature. +Derive the address from the account that was used to sign data with content type `text/plain` and the signature. #### Arguments - data [data]: data that was signed diff --git a/cmd/clef/extapi_changelog.md b/cmd/clef/extapi_changelog.md index 7adc33960590..25f819bddb00 100644 --- a/cmd/clef/extapi_changelog.md +++ b/cmd/clef/extapi_changelog.md @@ -15,7 +15,6 @@ The addition of `contentType` makes it possible to use the method for different * The external `account_Ecrecover`-method was removed. * The external `account_Import`-method was removed. - #### 3.0.0 * The external `account_List`-method was changed to not expose `url`, which contained info about the local filesystem. It now returns only a list of addresses. diff --git a/signer/core/api.go b/signer/core/api.go index 16968f7ad872..33ec28bb8a67 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -176,7 +176,6 @@ type ( ContentType string `json:"content_type"` Address common.MixedcaseAddress `json:"address"` Rawdata interface{} `json:"raw_data"` - //Message string `json:"message"` Message []*NameValueType `json:"message"` Hash hexutil.Bytes `json:"hash"` Meta Metadata `json:"meta"` diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 3f8f7da5ac75..bddb2b1001f3 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -29,17 +29,15 @@ import ( "strings" "unicode" - "golang.org/x/crypto/sha3" - - "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/accounts" "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/common/math" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/crypto/sha3" ) type SigFormat struct { @@ -118,7 +116,7 @@ type TypedDataDomain struct { Salt string `json:"salt"` } -var typedDataRegexp = regexp.MustCompile(`^((address|bool|bytes|string)|((bytes)([1-9]|[1-2][0-9]|3[0-2]))|((int|uint)(8|16|32|64|128|256)))(\[])?$`) +var typedDataRegexp = regexp.MustCompile(`^((address|bool|bytes|string)|((bytes)([1-9]|[1-2][0-9]|3[0-2]))|((int|uint)(8|16|32|64|128|256)))(\[\])?$`) var typedDataReferenceTypeRegexp = regexp.MustCompile(`^[A-Z](\w*)(\[])?$`) // Sign receives a request and produces a signature @@ -222,7 +220,7 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M }, } req = &SignDataRequest{ContentType: mediaType, Rawdata: cliqueData, Message: message, Hash: sighash} - case TextPlain.Mime: + default: // also case TextPlain.Mime: // Calculates an Ethereum ECDSA signature for: // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") plainData, err := hexutil.Decode(data.(string)) @@ -239,8 +237,6 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M } req = &SignDataRequest{ContentType: mediaType, Rawdata: plainData, Message: message, Hash: sighash} - default: - return nil, fmt.Errorf("content type '%s' not implemented for signing", contentType) } return req, nil @@ -315,7 +311,6 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd return nil, err } sighash := crypto.Keccak256([]byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))) - //output := typedData.PrettyPrint() message := typedData.Format() req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: typedData.Map(), Message: message, Hash: sighash} signature, err := api.Sign(ctx, addr, req) @@ -391,6 +386,7 @@ func (typedData *TypedData) EncodeType(primaryType string) hexutil.Bytes { return buffer.Bytes() } +// TypeHash creates the keccak256 hash of the data func (typedData *TypedData) TypeHash(primaryType string) hexutil.Bytes { return crypto.Keccak256(typedData.EncodeType(primaryType)) } @@ -628,7 +624,7 @@ func (typedData *TypedData) PrettyPrint() string { return output.String() } -// Format returns a representation of d, which can be easily displayed by a user-interface +// Format returns a representation of typedData, which can be easily displayed by a user-interface // without in-depth knowledge about 712 rules func (typedData *TypedData) Format() []*NameValueType { var nvts []*NameValueType diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index b424f05f0b22..63fb0402d063 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -275,7 +275,7 @@ func TestEncodeData(t *testing.T) { } } -func TestMalformedData1(t *testing.T) { +func TestMalformedDomainkeys(t *testing.T) { // Verifies that malformed domain keys are properly caught: //{ // "name": "Ether Mail", @@ -365,7 +365,7 @@ func TestMalformedData1(t *testing.T) { } } -func TestMalformedData2(t *testing.T) { +func TestTypeMismatch(t *testing.T) { // Verifies that: // 1. Mismatches between the given type and data, i.e. `Person` and // the data item is a string, are properly caught: @@ -472,7 +472,7 @@ func TestMalformedData2(t *testing.T) { } } -func TestMalformedData3(t *testing.T) { +func TestMalformedTypesAndExtradata(t *testing.T) { // Verifies several quirks // 1. Using dynamic types and only validating the prefix: //{ @@ -567,7 +567,7 @@ func TestMalformedData3(t *testing.T) { } } -func TestMalformedData4(t *testing.T) { +func TestTypeMismatch(t *testing.T) { // Verifies data that doesn't fit into it: //{ // "test": 65536 <-- test defined as uint8 @@ -606,19 +606,6 @@ func TestFormatter(t *testing.T) { if err != nil { t.Fatalf("unmarshalling failed %v", err) } - - //x := PrettyPrintPrimitiveValue("address", "wallet", "0x123123123", 1) - //fmt.Printf(x) - //y := FormatPrimitiveValue("address", "wallet", "0x123123123") - //fmt.Printf("%v\n", y) - - //fmt.Printf(d.PrettyPrintData(d.PrimaryType,d.Message, 1)) - - //formatted := d.FormatData(d.PrimaryType,d.Message) - //for _,item := range formatted{ - // fmt.Printf("%v\n", item.Pprint(0)) - //} - formatted := d.Format() for _, item := range formatted { fmt.Printf("%v\n", item.Pprint(0)) @@ -629,7 +616,7 @@ func TestFormatter(t *testing.T) { } -func TestMalformedData5(t *testing.T) { +func TestCustomTypeAsArray(t *testing.T) { var jsonTypedData = ` { "types": { From 8af9cda0703b142fb2f3a42ab98196533ea44963 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sun, 20 Jan 2019 17:01:19 +0000 Subject: [PATCH 78/84] Implemented latest recommendations --- cmd/clef/README.md | 4 +- signer/core/api.go | 6 +- signer/core/signed_data.go | 166 +++++++++++++++--- signer/core/signed_data_test.go | 292 ++++++++++++++++++++------------ 4 files changed, 332 insertions(+), 136 deletions(-) diff --git a/cmd/clef/README.md b/cmd/clef/README.md index 98e85d5a1159..2e1dac299803 100644 --- a/cmd/clef/README.md +++ b/cmd/clef/README.md @@ -383,8 +383,8 @@ Response ```json { - "jsonrpc": "2.0", "id": 3, + "jsonrpc": "2.0", "result": "0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c" } ``` @@ -480,8 +480,8 @@ Response ```json { - "jsonrpc": "2.0", "id": 1, + "jsonrpc": "2.0", "result": "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c" } ``` diff --git a/signer/core/api.go b/signer/core/api.go index 33ec28bb8a67..cfa09fb4c694 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -176,9 +176,9 @@ type ( ContentType string `json:"content_type"` Address common.MixedcaseAddress `json:"address"` Rawdata interface{} `json:"raw_data"` - Message []*NameValueType `json:"message"` - Hash hexutil.Bytes `json:"hash"` - Meta Metadata `json:"meta"` + Message []*NameValueType `json:"message"` + Hash hexutil.Bytes `json:"hash"` + Meta Metadata `json:"meta"` } SignDataResponse struct { Approved bool `json:"approved"` diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index bddb2b1001f3..710d0aa63e4c 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -116,8 +116,8 @@ type TypedDataDomain struct { Salt string `json:"salt"` } -var typedDataRegexp = regexp.MustCompile(`^((address|bool|bytes|string)|((bytes)([1-9]|[1-2][0-9]|3[0-2]))|((int|uint)(8|16|32|64|128|256)))(\[\])?$`) -var typedDataReferenceTypeRegexp = regexp.MustCompile(`^[A-Z](\w*)(\[])?$`) +// var typedDataRegexp = regexp.MustCompile(`^((address|bool|bytes|int|uint|string)|((bytes)([1-9]|[1-2][0-9]|3[0-2]))|((int|uint)(8|16|32|64|128|256)))(\[\])?$`) +var typedDataReferenceTypeRegexp = regexp.MustCompile(`^[A-Z](\w*)(\[\])?$`) // Sign receives a request and produces a signature @@ -299,9 +299,6 @@ func SignTextPlain(data hexutil.Bytes) (hexutil.Bytes, string) { // 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) { - if err := typedData.Validate(); err != nil { - return nil, err - } domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) if err != nil { return nil, err @@ -396,6 +393,10 @@ func (typedData *TypedData) TypeHash(primaryType string) hexutil.Bytes { // // each encoded member is 32-byte long func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}, depth int) (hexutil.Bytes, error) { + if err := typedData.validate(); err != nil { + return nil, err + } + buffer := bytes.Buffer{} // Verify extra data @@ -430,7 +431,7 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter } arrayBuffer.Write(encodedData) } else { - bytesValue, err := typedData.EncodePrimitiveValue(encType, encValue, depth) + bytesValue, err := typedData.EncodePrimitiveValue(parsedType, item, depth) if err != nil { return nil, err } @@ -495,15 +496,14 @@ func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interf } return crypto.Keccak256(bytesValue), nil } - // bytes32 etc if strings.HasPrefix(encType, "bytes") { - sizeStr := strings.TrimPrefix(encType, "bytes") - size, err := strconv.Atoi(sizeStr) + lengthStr := strings.TrimPrefix(encType, "bytes") + length, err := strconv.Atoi(lengthStr) if err != nil { - return nil, fmt.Errorf("invalid size on bytes: %v", sizeStr) + return nil, fmt.Errorf("invalid size on bytes: %v", lengthStr) } - if size < 0 || size > 32 { - return nil, fmt.Errorf("invalid size on bytes: %d", size) + if length < 0 || length > 32 { + return nil, fmt.Errorf("invalid size on bytes: %d", length) } if byteValue, ok := encValue.(hexutil.Bytes); !ok { return nil, dataMismatchError(encType, encValue) @@ -511,8 +511,22 @@ func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interf return math.PaddedBigBytes(new(big.Int).SetBytes(byteValue), 32), nil } } - if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { + if strings.HasPrefix(encType, "int") || strings.HasPrefix(encType, "uint") { + length := 0 + if encType == "int" || encType == "uint" { + length = 256 + } else { + lengthStr := strings.TrimPrefix(strings.TrimPrefix(encType, "uint"), "int") + atoiSize, err := strconv.Atoi(lengthStr) + if err != nil { + return nil, fmt.Errorf("invalid size on integer: %v", lengthStr) + } + length = atoiSize + } bigIntValue, ok := encValue.(*big.Int) + if bigIntValue.BitLen() > length { + return nil, fmt.Errorf("integer larger than '%v'", encType) + } if !ok { return nil, dataMismatchError(encType, encValue) } @@ -591,12 +605,12 @@ func UnmarshalValidatorData(data interface{}) (ValidatorData, error) { }, nil } -// Validate make sure the types are sound -func (typedData *TypedData) Validate() error { - if err := typedData.Types.Validate(); err != nil { +// Validate makes sure the types are sound +func (typedData *TypedData) validate() error { + if err := typedData.Types.validate(); err != nil { return err } - if err := typedData.Domain.Validate(); err != nil { + if err := typedData.Domain.validate(); err != nil { return err } return nil @@ -724,7 +738,7 @@ func (nvt *NameValueType) Pprint(depth int) string { } // Validate checks if the types object is conformant to the specs -func (t Types) Validate() error { +func (t Types) validate() error { for typeKey, typeArr := range t { for _, typeObj := range typeArr { if typeKey == typeObj.Type { @@ -737,8 +751,7 @@ func (t Types) Validate() error { if !typedDataReferenceTypeRegexp.MatchString(typeObj.Type) { return fmt.Errorf("unknown reference type '%s", typeObj.Type) } - - } else if !typedDataRegexp.MatchString(typeObj.Type) { + } else if !isPrimitiveTypeValid(typeObj.Type) { return fmt.Errorf("unknown type '%s'", typeObj.Type) } } @@ -746,9 +759,120 @@ func (t Types) Validate() error { return nil } +// Checks if the primitive value is valid +func isPrimitiveTypeValid(primitiveType string) bool { + if primitiveType == "address" || + primitiveType == "address[]" || + primitiveType == "bool" || + primitiveType == "bool[]" || + primitiveType == "string" || + primitiveType == "string[]" { + return true + } + if primitiveType == "bytes" || + primitiveType == "bytes[]" || + primitiveType == "bytes1" || + primitiveType == "bytes1[]" || + primitiveType == "bytes2" || + primitiveType == "bytes2[]" || + primitiveType == "bytes3" || + primitiveType == "bytes3[]" || + primitiveType == "bytes4" || + primitiveType == "bytes4[]" || + primitiveType == "bytes5" || + primitiveType == "bytes5[]" || + primitiveType == "bytes6" || + primitiveType == "bytes6[]" || + primitiveType == "bytes7" || + primitiveType == "bytes7[]" || + primitiveType == "bytes8" || + primitiveType == "bytes8[]" || + primitiveType == "bytes9" || + primitiveType == "bytes9[]" || + primitiveType == "bytes10" || + primitiveType == "bytes10[]" || + primitiveType == "bytes11" || + primitiveType == "bytes11[]" || + primitiveType == "bytes12" || + primitiveType == "bytes12[]" || + primitiveType == "bytes13" || + primitiveType == "bytes13[]" || + primitiveType == "bytes14" || + primitiveType == "bytes14[]" || + primitiveType == "bytes15" || + primitiveType == "bytes15[]" || + primitiveType == "bytes16" || + primitiveType == "bytes16[]" || + primitiveType == "bytes17" || + primitiveType == "bytes17[]" || + primitiveType == "bytes18" || + primitiveType == "bytes18[]" || + primitiveType == "bytes19" || + primitiveType == "bytes19[]" || + primitiveType == "bytes20" || + primitiveType == "bytes20[]" || + primitiveType == "bytes21" || + primitiveType == "bytes21[]" || + primitiveType == "bytes22" || + primitiveType == "bytes22[]" || + primitiveType == "bytes23" || + primitiveType == "bytes23[]" || + primitiveType == "bytes24" || + primitiveType == "bytes24[]" || + primitiveType == "bytes25" || + primitiveType == "bytes25[]" || + primitiveType == "bytes26" || + primitiveType == "bytes26[]" || + primitiveType == "bytes27" || + primitiveType == "bytes27[]" || + primitiveType == "bytes28" || + primitiveType == "bytes28[]" || + primitiveType == "bytes29" || + primitiveType == "bytes29[]" || + primitiveType == "bytes30" || + primitiveType == "bytes30[]" || + primitiveType == "bytes31" || + primitiveType == "bytes31[]" { + return true + } + if primitiveType == "int" || + primitiveType == "int[]" || + primitiveType == "int8" || + primitiveType == "int8[]" || + primitiveType == "int16" || + primitiveType == "int16[]" || + primitiveType == "int32" || + primitiveType == "int32[]" || + primitiveType == "int64" || + primitiveType == "int64[]" || + primitiveType == "int128" || + primitiveType == "int128[]" || + primitiveType == "int256" || + primitiveType == "int256[]" { + return true + } + if primitiveType == "uint" || + primitiveType == "uint[]" || + primitiveType == "uint8" || + primitiveType == "uint8[]" || + primitiveType == "uint16" || + primitiveType == "uint16[]" || + primitiveType == "uint32" || + primitiveType == "uint32[]" || + primitiveType == "uint64" || + primitiveType == "uint64[]" || + primitiveType == "uint128" || + primitiveType == "uint128[]" || + primitiveType == "uint256" || + primitiveType == "uint256[]" { + return true + } + return false +} + // Validate checks if the given domain is valid, i.e. contains at least // the minimum viable keys and values -func (domain *TypedDataDomain) Validate() error { +func (domain *TypedDataDomain) validate() error { if domain.ChainId == big.NewInt(0) { return errors.New("chainId must be specified according to EIP-155") } diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 63fb0402d063..7d44bce2ce72 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -193,7 +193,7 @@ func TestSignData(t *testing.T) { t.Errorf("Expected nil-data, got %x", signature) } if err != keystore.ErrDecrypt { - t.Errorf("Expected ErrLocked! %v", err) + t.Errorf("Expected ErrLocked! '%v'", err) } control <- "No way" signature, err = api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world"))) @@ -201,7 +201,7 @@ func TestSignData(t *testing.T) { t.Errorf("Expected nil-data, got %x", signature) } if err != ErrRequestDenied { - t.Errorf("Expected ErrRequestDenied! %v", err) + t.Errorf("Expected ErrRequestDenied! '%v'", err) } // text/plain control <- "Y" @@ -283,7 +283,7 @@ func TestMalformedDomainkeys(t *testing.T) { // "chainId": 1, // "vxerifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" //} - var jsonTypedData = ` + jsonTypedData := ` { "types": { "EIP712Domain": [ @@ -348,38 +348,28 @@ func TestMalformedDomainkeys(t *testing.T) { "contents": "Hello, Bob!" } } - ` - var malformedTypedData TypedData - err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) - if err != nil { - t.Fatalf("unmarshalling failed %v", err) - } - err = malformedTypedData.Validate() + var malformedDomainTypedData TypedData + err := json.Unmarshal([]byte(jsonTypedData), &malformedDomainTypedData) if err != nil { - t.Fatalf("Expected no error, got %v", err) + t.Fatalf("unmarshalling failed '%v'", err) } - _, err = malformedTypedData.HashStruct("EIP712Domain", malformedTypedData.Domain.Map()) + _, err = malformedDomainTypedData.HashStruct("EIP712Domain", malformedDomainTypedData.Domain.Map()) if err == nil || err.Error() != "provided data '' doesn't match type 'address'" { - t.Errorf("Expected `provided data '' doesn't match type 'address'`, got %v", err) + t.Errorf("Expected `provided data '' doesn't match type 'address'`, got '%v'", err) } } -func TestTypeMismatch(t *testing.T) { - // Verifies that: - // 1. Mismatches between the given type and data, i.e. `Person` and - // the data item is a string, are properly caught: - //{ - // "name": "contents", - // "type": "Person" - //}, +func TestMalformedTypesAndExtradata(t *testing.T) { + // Verifies several quirks + // 1. Using dynamic types and only validating the prefix: //{ - // "contents": "Hello, Bob!" <-- string not "Person" + // "name": "chainId", + // "type": "uint256 ... and now for something completely different" //} - // 2. Nonexistent types are properly caught: + // 2. Extra data in message: //{ - // "name": "contents", - // "type": "Blahonga" + // "blahonga": "zonk bonk" //} jsonTypedData := ` { @@ -395,7 +385,7 @@ func TestTypeMismatch(t *testing.T) { }, { "name": "chainId", - "type": "uint256" + "type": "uint256 ... and now for something completely different" }, { "name": "verifyingContract", @@ -423,7 +413,7 @@ func TestTypeMismatch(t *testing.T) { }, { "name": "contents", - "type": "Person" + "type": "string" } ] }, @@ -432,12 +422,12 @@ func TestTypeMismatch(t *testing.T) { "name": "Ether Mail", "version": "1", "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" }, "message": { "from": { "name": "Cow", - "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + "wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" }, "to": { "name": "Bob", @@ -450,38 +440,32 @@ func TestTypeMismatch(t *testing.T) { var malformedTypedData TypedData err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) if err != nil { - t.Fatalf("unmarshalling failed %v", err) - } - err = malformedTypedData.Validate() - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) - if err.Error() != "provided data 'Hello, Bob!' doesn't match type 'Person'" { - t.Errorf("Expected `provided data 'Hello, Bob!' doesn't match type 'Person'`, got %v", err) + t.Fatalf("unmarshalling failed '%v'", err) } - malformedTypedData.Types["Mail"][2].Type = "Blahonga" - err = malformedTypedData.Validate() - if err == nil || err.Error() != "reference type 'Blahonga' is undefined" { - t.Fatalf("Expected `reference type 'Blahonga' is undefined`, got %v", err) - } + malformedTypedData.Types["EIP712Domain"][2].Type = "uint256" + malformedTypedData.Message["blahonga"] = "zonk bonk" _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) - if err == nil || err.Error() != "unrecognized type 'Blahonga'" { - t.Errorf("Expected `unrecognized type 'Blahonga'`, got %v", err) + if err == nil || err.Error() != "there is extra data provided in the message" { + t.Errorf("Expected `there is extra data provided in the message`, got '%v'", err) } } -func TestMalformedTypesAndExtradata(t *testing.T) { - // Verifies several quirks - // 1. Using dynamic types and only validating the prefix: +func TestTypeMismatch(t *testing.T) { + // Verifies that: + // 1. Mismatches between the given type and data, i.e. `Person` and + // the data item is a string, are properly caught: //{ - // "name": "chainId", - // "type": "uint256 ... and now for something completely different" + // "name": "contents", + // "type": "Person" + //}, + //{ + // "contents": "Hello, Bob!" <-- string not "Person" //} - // 2. Extra data in message: + // 2. Nonexistent types are properly caught: //{ - // "blahonga": "zonk bonk" + // "name": "contents", + // "type": "Blahonga" //} jsonTypedData := ` { @@ -497,7 +481,7 @@ func TestMalformedTypesAndExtradata(t *testing.T) { }, { "name": "chainId", - "type": "uint256 ... and now for something completely different" + "type": "uint256" }, { "name": "verifyingContract", @@ -525,7 +509,7 @@ func TestMalformedTypesAndExtradata(t *testing.T) { }, { "name": "contents", - "type": "string" + "type": "Person" } ] }, @@ -534,12 +518,12 @@ func TestMalformedTypesAndExtradata(t *testing.T) { "name": "Ether Mail", "version": "1", "chainId": 1, - "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" }, "message": { "from": { "name": "Cow", - "wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" }, "to": { "name": "Bob", @@ -549,71 +533,146 @@ func TestMalformedTypesAndExtradata(t *testing.T) { } } ` - var malformedTypedData TypedData - err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) + var mismatchTypedData TypedData + err := json.Unmarshal([]byte(jsonTypedData), &mismatchTypedData) if err != nil { - t.Fatalf("unmarshalling failed %v", err) + t.Fatalf("unmarshalling failed '%v'", err) } - err = malformedTypedData.Validate() - if err == nil || err.Error() != "unknown type 'uint256 ... and now for something completely different'" { - t.Fatalf("Expected `unknown type 'uint256 ... and now for something completely different'`, got %v", err) + _, err = mismatchTypedData.HashStruct(mismatchTypedData.PrimaryType, mismatchTypedData.Message) + if err.Error() != "provided data 'Hello, Bob!' doesn't match type 'Person'" { + t.Errorf("Expected `provided data 'Hello, Bob!' doesn't match type 'Person'`, got '%v'", err) } - malformedTypedData.Types["EIP712Domain"][2].Type = "uint256" - malformedTypedData.Message["blahonga"] = "zonk bonk" - _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) - if err == nil || err.Error() != "there is extra data provided in the message" { - t.Errorf("Expected `there is extra data provided in the message`, got %v", err) + mismatchTypedData.Types["Mail"][2].Type = "Blahonga" + _, err = mismatchTypedData.HashStruct(mismatchTypedData.PrimaryType, mismatchTypedData.Message) + if err == nil || err.Error() != "reference type 'Blahonga' is undefined" { + t.Fatalf("Expected `reference type 'Blahonga' is undefined`, got '%v'", err) } } -func TestTypeMismatch(t *testing.T) { +func TestTypeOverflow(t *testing.T) { // Verifies data that doesn't fit into it: //{ // "test": 65536 <-- test defined as uint8 //} - var malformedTypedData TypedData - err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) + var overflowTypedData TypedData + err := json.Unmarshal([]byte(jsonTypedData), &overflowTypedData) if err != nil { - t.Fatalf("unmarshalling failed %v", err) + t.Fatalf("unmarshalling failed '%v'", err) } // Set test to something outside uint8 - (malformedTypedData.Message["from"]).(map[string]interface{})["test"] = 65536 - - err = malformedTypedData.Validate() - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } + (overflowTypedData.Message["from"]).(map[string]interface{})["test"] = big.NewInt(65536) - _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) - if err == nil || err.Error() != "provided data '65536' doesn't match type 'uint8'" { - t.Fatalf("Expected `provided data '65536' doesn't match type 'uint8'`, got %v", err) + _, err = overflowTypedData.HashStruct(overflowTypedData.PrimaryType, overflowTypedData.Message) + if err == nil || err.Error() != "integer larger than 'uint8'" { + t.Fatalf("Expected `integer larger than 'uint8'`, got '%v'", err) } - (malformedTypedData.Message["from"]).(map[string]interface{})["test"] = big.NewInt(3) - (malformedTypedData.Message["to"]).(map[string]interface{})["test"] = big.NewInt(4) + (overflowTypedData.Message["from"]).(map[string]interface{})["test"] = big.NewInt(3) + (overflowTypedData.Message["to"]).(map[string]interface{})["test"] = big.NewInt(4) - _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) + _, err = overflowTypedData.HashStruct(overflowTypedData.PrimaryType, overflowTypedData.Message) if err != nil { - t.Fatalf("Expected no err, got %v", err) + t.Fatalf("Expected no err, got '%v'", err) } } -func TestFormatter(t *testing.T) { +func TestArray(t *testing.T) { + // Makes sure that arrays work fine + //{ + // "type": "address[]" + //}, + //{ + // "type": "string[]" + //}, + //{ + // "type": "uint16[]", + //} - var d TypedData - err := json.Unmarshal([]byte(jsonTypedData), &d) + jsonTypedData := ` + { + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Foo": [ + { + "name": "bar", + "type": "address[]" + } + ] + }, + "primaryType": "Foo", + "domain": { + "name": "Lorem", + "version": "1", + "chainId": 1, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "bar": [ + "0x0000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000003" + ] + } + } + ` + var arrayTypedData TypedData + err := json.Unmarshal([]byte(jsonTypedData), &arrayTypedData) if err != nil { - t.Fatalf("unmarshalling failed %v", err) + t.Fatalf("unmarshalling failed '%v'", err) } - formatted := d.Format() - for _, item := range formatted { - fmt.Printf("%v\n", item.Pprint(0)) + _, err = arrayTypedData.HashStruct(arrayTypedData.PrimaryType, arrayTypedData.Message) + if err != nil { + t.Fatalf("Expected no err, got '%v'", err) } - j, _ := json.Marshal(formatted) - fmt.Printf("%v\n", string(j)) + // Change array to string + arrayTypedData.Types["Foo"][0].Type = "string[]" + arrayTypedData.Message["bar"] = []interface{}{ + "lorem", + "ipsum", + "dolores", + } + _, err = arrayTypedData.HashStruct(arrayTypedData.PrimaryType, arrayTypedData.Message) + if err != nil { + t.Fatalf("Expected no err, got '%v'", err) + } + + // Change array to uint + arrayTypedData.Types["Foo"][0].Type = "uint[]" + arrayTypedData.Message["bar"] = []interface{}{ + big.NewInt(1955), + big.NewInt(108), + big.NewInt(44010), + } + _, err = arrayTypedData.HashStruct(arrayTypedData.PrimaryType, arrayTypedData.Message) + if err != nil { + t.Fatalf("Expected no err, got '%v'", err) + } + // Should not work with fixed-size arrays + arrayTypedData.Types["Foo"][0].Type = "uint[3]" + _, err = arrayTypedData.HashStruct(arrayTypedData.PrimaryType, arrayTypedData.Message) + if err == nil || err.Error() != "unknown type 'uint[3]'" { + t.Fatalf("Expected `unknown type 'uint[3]'`, got '%v'", err) + } } func TestCustomTypeAsArray(t *testing.T) { @@ -626,44 +685,45 @@ func TestCustomTypeAsArray(t *testing.T) { "type": "string" }, { - "name": "version", + "name": "version", "type": "string" }, { - "name": "chainId", + "name": "chainId", "type": "uint256" }, { - "name": "verifyingContract", + "name": "verifyingContract", "type": "address" } ], "Person": [ { - "name": "name", + "name": "name", "type": "string" }, { - "name": "wallet", + "name": "wallet", "type": "address" } ], "Person[]": [ { - "name": "baz", + "name": "baz", "type": "string" - }], + } + ], "Mail": [ { - "name": "from", + "name": "from", "type": "Person" }, { - "name": "to", + "name": "to", "type": "Person[]" }, { - "name": "contents", + "name": "contents", "type": "string" } ] @@ -677,8 +737,8 @@ func TestCustomTypeAsArray(t *testing.T) { }, "message": { "from": { - "name": "Cow", - "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" }, "to": {"baz": "foo"}, "contents": "Hello, Bob!" @@ -689,14 +749,26 @@ func TestCustomTypeAsArray(t *testing.T) { var malformedTypedData TypedData err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) if err != nil { - t.Fatalf("unmarshalling failed %v", err) + t.Fatalf("unmarshalling failed '%v'", err) } - err = malformedTypedData.Validate() + _, err = malformedTypedData.HashStruct("EIP712Domain", malformedTypedData.Domain.Map()) if err != nil { - t.Fatalf("Expected no error, got %v", err) + t.Errorf("Expected no error, got '%v'", err) } - _, err = malformedTypedData.HashStruct("EIP712Domain", malformedTypedData.Domain.Map()) +} + +func TestFormatter(t *testing.T) { + + var d TypedData + err := json.Unmarshal([]byte(jsonTypedData), &d) if err != nil { - t.Errorf("Expected no error, got %v", err) + t.Fatalf("unmarshalling failed '%v'", err) } + formatted := d.Format() + for _, item := range formatted { + fmt.Printf("'%v'\n", item.Pprint(0)) + } + + j, _ := json.Marshal(formatted) + fmt.Printf("'%v'\n", string(j)) } From 38e1d159eb6585f69d5827d886b89c29deffb04a Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Wed, 30 Jan 2019 00:08:54 +0000 Subject: [PATCH 79/84] Fixed comments and uintint256 issue --- signer/core/signed_data.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 710d0aa63e4c..3298d4b71c96 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -116,7 +116,6 @@ type TypedDataDomain struct { Salt string `json:"salt"` } -// var typedDataRegexp = regexp.MustCompile(`^((address|bool|bytes|int|uint|string)|((bytes)([1-9]|[1-2][0-9]|3[0-2]))|((int|uint)(8|16|32|64|128|256)))(\[\])?$`) var typedDataReferenceTypeRegexp = regexp.MustCompile(`^[A-Z](\w*)(\[\])?$`) // Sign receives a request and produces a signature @@ -170,7 +169,7 @@ func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr com return signature, nil } -// Determines which signature method should be used based upon the mime type +// determineSignatureFormat determines which signature method should be used based upon the mime type // In the cases where it matters ensure that the charset is handled. The charset // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType // charset, ok := params["charset"] @@ -516,7 +515,12 @@ func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interf if encType == "int" || encType == "uint" { length = 256 } else { - lengthStr := strings.TrimPrefix(strings.TrimPrefix(encType, "uint"), "int") + lengthStr := "" + if strings.HasPrefix(encType, "uint") { + lengthStr = strings.TrimPrefix(encType, "uint") + } else { + lengthStr = strings.TrimPrefix(encType, "int") + } atoiSize, err := strconv.Atoi(lengthStr) if err != nil { return nil, fmt.Errorf("invalid size on integer: %v", lengthStr) @@ -605,7 +609,7 @@ func UnmarshalValidatorData(data interface{}) (ValidatorData, error) { }, nil } -// Validate makes sure the types are sound +// validate makes sure the types are sound func (typedData *TypedData) validate() error { if err := typedData.Types.validate(); err != nil { return err @@ -870,7 +874,7 @@ func isPrimitiveTypeValid(primitiveType string) bool { return false } -// Validate checks if the given domain is valid, i.e. contains at least +// validate checks if the given domain is valid, i.e. contains at least // the minimum viable keys and values func (domain *TypedDataDomain) validate() error { if domain.ChainId == big.NewInt(0) { From a51bea0cb95c81500ee8f1ecd722ce3d40acf21e Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 5 Feb 2019 13:18:53 +0100 Subject: [PATCH 80/84] accounts, signer: fix mimetypes, add interface to sign data with passphrase --- accounts/accounts.go | 29 ++++++++------ accounts/external/backend.go | 9 +++-- accounts/keystore/wallet.go | 30 +++++++++----- accounts/usbwallet/wallet.go | 7 ++++ consensus/clique/clique.go | 2 +- signer/core/api.go | 50 ------------------------ signer/core/signed_data.go | 76 ++++++++++++++++++------------------ 7 files changed, 90 insertions(+), 113 deletions(-) diff --git a/accounts/accounts.go b/accounts/accounts.go index 11232b19a0e6..93ecf179b54e 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -35,6 +35,13 @@ type Account struct { URL URL `json:"url"` // Optional resource locator within a backend } +const ( + MimetypeTextWithValidator = "text/validator" + MimetypeTypedData = "data/typed" + MimetypeClique = "application/x-clique-header" + MimetypeTextPlain = "text/plain" +) + // Wallet represents a software or hardware wallet that might contain one or more // accounts (derived from the same seed). type Wallet interface { @@ -101,6 +108,12 @@ type Wallet interface { // the account in a keystore). SignData(account Account, mimeType string, data []byte) ([]byte, error) + // SignDataWithPassphrase is identical to SignData, but also takes a password + // NOTE: there's an chance that an erroneous call might mistake the two strings, and + // supply password in the mimetype field, or vice versa. Thus, an implementation + // should never echo the mimetype or return the mimetype in the error-response + SignDataWithPassphrase(account Account, passphrase, mimeType string, data []byte) ([]byte, error) + // Signtext requests the wallet to sign the hash of a given piece of data, prefixed // by the Ethereum prefix scheme // It looks up the account specified either solely via its address contained within, @@ -114,6 +127,9 @@ type Wallet interface { // the account in a keystore). SignText(account Account, text []byte) ([]byte, error) + // SignTextWithPassphrase is identical to Signtext, but also takes a password + SignTextWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error) + // SignTx requests the wallet to sign the given transaction. // // It looks up the account specified either solely via its address contained within, @@ -127,18 +143,7 @@ type Wallet interface { // the account in a keystore). SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) - // SignTextWithPassphrase requests the wallet to sign the given text with the - // given passphrase as extra authentication information. - // - // It looks up the account specified either solely via its address contained within, - // or optionally with the aid of any location metadata from the embedded URL field. - SignTextWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error) - - // SignTxWithPassphrase requests the wallet to sign the given transaction, with the - // given passphrase as extra authentication information. - // - // It looks up the account specified either solely via its address contained within, - // or optionally with the aid of any location metadata from the embedded URL field. + // SignTxWithPassphrase is identical to SignTx, but also takes a password SignTxWithPassphrase(account Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) } diff --git a/accounts/external/backend.go b/accounts/external/backend.go index 35b9c276d179..3b8d50f1b699 100644 --- a/accounts/external/backend.go +++ b/accounts/external/backend.go @@ -184,11 +184,14 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio } func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { - return []byte{}, fmt.Errorf("operation not supported on external signers") + return []byte{}, fmt.Errorf("passphrase-operations not supported on external signers") } func (api *ExternalSigner) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { - return nil, fmt.Errorf("operation not supported on external signers") + return nil, fmt.Errorf("passphrase-operations not supported on external signers") +} +func (api *ExternalSigner) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { + return nil, fmt.Errorf("passphrase-operations not supported on external signers") } func (api *ExternalSigner) listAccounts() ([]common.Address, error) { @@ -201,7 +204,7 @@ func (api *ExternalSigner) listAccounts() ([]common.Address, error) { func (api *ExternalSigner) signCliqueBlock(a common.Address, rlpBlock hexutil.Bytes) (hexutil.Bytes, error) { var sig hexutil.Bytes - if err := api.client.Call(&sig, "account_signData", "application/clique", a, rlpBlock); err != nil { + if err := api.client.Call(&sig, "account_signData", core.ApplicationClique.Mime, a, rlpBlock); err != nil { return nil, err } if sig[64] != 27 && sig[64] != 28 { diff --git a/accounts/keystore/wallet.go b/accounts/keystore/wallet.go index 0490f39ff533..632620ead97c 100644 --- a/accounts/keystore/wallet.go +++ b/accounts/keystore/wallet.go @@ -97,21 +97,18 @@ func (w *keystoreWallet) SignData(account accounts.Account, mimeType string, dat return w.signHash(account, crypto.Keccak256(data)) } -func (w *keystoreWallet) SignText(account accounts.Account, text []byte) ([]byte, error) { - return w.signHash(account, accounts.TextHash(text)) -} - -// SignTx implements accounts.Wallet, attempting to sign the given transaction -// with the given account. If the wallet does not wrap this particular account, -// an error is returned to avoid account leakage (even though in theory we may -// be able to sign via our shared keystore backend). -func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { +// SignDataWithPassphrase signs keccak256(data). The mimetype parameter describes the type of data being signed +func (w *keystoreWallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { // Make sure the requested account is contained within if !w.Contains(account) { return nil, accounts.ErrUnknownAccount } // Account seems valid, request the keystore to sign - return w.keystore.SignTx(account, tx, chainID) + return w.keystore.SignHashWithPassphrase(account, passphrase, crypto.Keccak256(data)) +} + +func (w *keystoreWallet) SignText(account accounts.Account, text []byte) ([]byte, error) { + return w.signHash(account, accounts.TextHash(text)) } // SignHashWithPassphrase implements accounts.Wallet, attempting to sign the @@ -125,6 +122,19 @@ func (w *keystoreWallet) SignTextWithPassphrase(account accounts.Account, passph return w.keystore.SignHashWithPassphrase(account, passphrase, accounts.TextHash(text)) } +// SignTx implements accounts.Wallet, attempting to sign the given transaction +// with the given account. If the wallet does not wrap this particular account, +// an error is returned to avoid account leakage (even though in theory we may +// be able to sign via our shared keystore backend). +func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + // Make sure the requested account is contained within + if !w.Contains(account) { + return nil, accounts.ErrUnknownAccount + } + // Account seems valid, request the keystore to sign + return w.keystore.SignTx(account, tx, chainID) +} + // SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given // transaction with the given account using passphrase as extra authentication. func (w *keystoreWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { diff --git a/accounts/usbwallet/wallet.go b/accounts/usbwallet/wallet.go index a99dcd0f5ad0..feab505c9b47 100644 --- a/accounts/usbwallet/wallet.go +++ b/accounts/usbwallet/wallet.go @@ -507,6 +507,13 @@ func (w *wallet) SignData(account accounts.Account, mimeType string, data []byte return w.signHash(account, crypto.Keccak256(data)) } +// SignDataWithPassphrase implements accounts.Wallet, attempting to sign the given +// data with the given account using passphrase as extra authentication. +// Since USB wallets don't rely on passphrases, these are silently ignored. +func (w *wallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { + return w.SignData(account, mimeType, data) +} + func (w *wallet) SignText(account accounts.Account, text []byte) ([]byte, error) { return w.signHash(account, accounts.TextHash(text)) } diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index c0f78ce65545..967a843dec59 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -616,7 +616,7 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results c log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle)) } // Sign all the things! - sighash, err := signFn(accounts.Account{Address: signer}, "application/x-clique-header", CliqueRLP(header)) + sighash, err := signFn(accounts.Account{Address: signer}, accounts.MimetypeClique, CliqueRLP(header)) if err != nil { return err } diff --git a/signer/core/api.go b/signer/core/api.go index f9937abac463..754aab11cba3 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -521,56 +521,6 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth } -// Sign calculates an Ethereum ECDSA signature for: -// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) -// -// Note, the produced signature conforms to the secp256k1 curve R, S and V values, -// where the V value will be 27 or 28 for legacy reasons. -// -// The key used to calculate the signature is decrypted with the given password. -// -// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign -func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { - sighash, msg := SignHash(data) - // We make the request prior to looking up if we actually have the account, to prevent - // account-enumeration via the API - req := &SignDataRequest{Address: addr, Rawdata: data, Message: msg, Hash: sighash, Meta: MetadataFromContext(ctx)} - res, err := api.UI.ApproveSignData(req) - - if err != nil { - return nil, err - } - if !res.Approved { - return nil, ErrRequestDenied - } - // Look up the wallet containing the requested signer - account := accounts.Account{Address: addr.Address()} - wallet, err := api.am.Find(account) - if err != nil { - return nil, err - } - // Assemble sign the data with the wallet - signature, err := wallet.SignTextWithPassphrase(account, res.Password, data) - if err != nil { - api.UI.ShowError(err.Error()) - return nil, err - } - signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper - return signature, nil -} - -// SignHash is a helper function that calculates a hash for the given message that can be -// safely used to calculate a signature from. -// -// The hash is calculated as -// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). -// -// This gives context to the signed message and prevents signing of transactions. -func SignHash(data []byte) ([]byte, string) { - msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) - return crypto.Keccak256([]byte(msg)), msg -} - // Export returns encrypted private key associated with the given address in web3 keystore format. func (api *SignerAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) { res, err := api.UI.ApproveExport(&ExportRequest{Address: addr, Meta: MetadataFromContext(ctx)}) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 3298d4b71c96..92bf9db9d438 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -21,6 +21,7 @@ import ( "context" "errors" "fmt" + "github.com/ethereum/go-ethereum/consensus/clique" "math/big" "mime" "regexp" @@ -37,7 +38,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" - "golang.org/x/crypto/sha3" ) type SigFormat struct { @@ -47,19 +47,19 @@ type SigFormat struct { var ( TextValidator = SigFormat{ - "text/validator", + accounts.MimetypeTextWithValidator, 0x00, } DataTyped = SigFormat{ - "data/typed", + accounts.MimetypeTypedData, 0x01, } ApplicationClique = SigFormat{ - "application/clique", + accounts.MimetypeClique, 0x02, } TextPlain = SigFormat{ - "text/plain", + accounts.MimetypeTextPlain, 0x45, } ) @@ -118,13 +118,11 @@ type TypedDataDomain struct { var typedDataReferenceTypeRegexp = regexp.MustCompile(`^[A-Z](\w*)(\[\])?$`) -// Sign receives a request and produces a signature +// sign receives a request and produces a signature // Note, the produced signature conforms to the secp256k1 curve R, S and V values, // where the V value will be 27 or 28 for legacy reasons. -func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, req *SignDataRequest) (hexutil.Bytes, error) { - req.Address = addr - req.Meta = MetadataFromContext(ctx) +func (api *SignerAPI) sign(ctx context.Context, addr common.MixedcaseAddress, req *SignDataRequest) (hexutil.Bytes, error) { // We make the request prior to looking up if we actually have the account, to prevent // account-enumeration via the API @@ -142,7 +140,7 @@ func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, re return nil, err } // Sign the data with the wallet - signature, err := wallet.SignHashWithPassphrase(account, res.Password, req.Hash) + signature, err := wallet.SignDataWithPassphrase(account, res.Password, req.ContentType, req.Hash) if err != nil { return nil, err } @@ -155,12 +153,12 @@ func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, re // // Different types of validation occur. func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error) { - var req, err = api.determineSignatureFormat(contentType, addr, data) + var req, err = api.determineSignatureFormat(ctx, contentType, addr, data) if err != nil { return nil, err } - signature, err := api.Sign(ctx, addr, req) + signature, err := api.sign(ctx, addr, req) if err != nil { api.UI.ShowError(err.Error()) return nil, err @@ -174,8 +172,12 @@ func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr com // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType // charset, ok := params["charset"] // As it is now, we accept any charset and just treat it as 'raw'. -func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.MixedcaseAddress, data interface{}) (*SignDataRequest, error) { +// This method returns the mimetype for signing along with the request +func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (*SignDataRequest, error) { var req *SignDataRequest + req.Address = addr + req.Meta = MetadataFromContext(ctx) + mediaType, _, err := mime.ParseMediaType(contentType) if err != nil { return nil, err @@ -234,7 +236,6 @@ func (api *SignerAPI) determineSignatureFormat(contentType string, addr common.M Value: msg, }, } - req = &SignDataRequest{ContentType: mediaType, Rawdata: plainData, Message: message, Hash: sighash} } return req, nil @@ -258,29 +259,30 @@ func SignTextValidator(validatorData ValidatorData) (hexutil.Bytes, string) { // in clique.go panics if this is the case, thus it's been reimplemented here to avoid the panic // and simply return an error instead func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { - hash := common.Hash{} + //hash := common.Hash{} if len(header.Extra) < 65 { - return hash.Bytes(), fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) - } - hasher := sha3.NewLegacyKeccak256() - rlp.Encode(hasher, []interface{}{ - header.ParentHash, - header.UncleHash, - header.Coinbase, - header.Root, - header.TxHash, - header.ReceiptHash, - header.Bloom, - header.Difficulty, - header.Number, - header.GasLimit, - header.GasUsed, - header.Time, - header.Extra[:len(header.Extra)-65], - header.MixDigest, - header.Nonce, - }) - hasher.Sum(hash[:0]) + return nil, fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) + } + //hasher := sha3.NewLegacyKeccak256() + hash := clique.SealHash(header) + //rlp.Encode(hasher, []interface{}{ + // header.ParentHash, + // header.UncleHash, + // header.Coinbase, + // header.Root, + // header.TxHash, + // header.ReceiptHash, + // header.Bloom, + // header.Difficulty, + // header.Number, + // header.GasLimit, + // header.GasUsed, + // header.Time, + // header.Extra[:len(header.Extra)-65], + // header.MixDigest, + // header.Nonce, + //}) + //hasher.Sum(hash[:0]) return hash.Bytes(), nil } @@ -309,7 +311,7 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd sighash := crypto.Keccak256([]byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))) message := typedData.Format() req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: typedData.Map(), Message: message, Hash: sighash} - signature, err := api.Sign(ctx, addr, req) + signature, err := api.sign(ctx, addr, req) if err != nil { api.UI.ShowError(err.Error()) return nil, err From 38ff1c570ccf37b15b04ebe50a5096335677a34e Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 5 Feb 2019 14:12:05 +0100 Subject: [PATCH 81/84] signer, accounts: remove duplicated code, pass hash preimages to signing --- accounts/accounts.go | 19 +++++++++++--- signer/core/api.go | 2 +- signer/core/signed_data.go | 51 +++++++++----------------------------- 3 files changed, 28 insertions(+), 44 deletions(-) diff --git a/accounts/accounts.go b/accounts/accounts.go index 93ecf179b54e..e8031e8b3b03 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -24,8 +24,8 @@ import ( ethereum "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/event" - "golang.org/x/crypto/sha3" ) // Account represents an Ethereum account located at a specific location defined @@ -175,9 +175,20 @@ type Backend interface { // // This gives context to the signed message and prevents signing of transactions. func TextHash(data []byte) []byte { - hash := sha3.NewLegacyKeccak256() - fmt.Fprintf(hash, "\x19Ethereum Signed Message:\n%d%s", len(data), data) - return hash.Sum(nil) + hash, _ := TextAndHash(data) + return hash +} + +// TextAndHash is a helper function that calculates a hash for the given message that can be +// safely used to calculate a signature from. +// +// The hash is calulcated as +// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). +// +// This gives context to the signed message and prevents signing of transactions. +func TextAndHash(data []byte) ([]byte, string) { + msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data)) + return crypto.Keccak256([]byte(msg)), msg } // WalletEventType represents the different event types that can be fired by diff --git a/signer/core/api.go b/signer/core/api.go index 754aab11cba3..0521dce47bac 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -182,7 +182,7 @@ type ( SignDataRequest struct { ContentType string `json:"content_type"` Address common.MixedcaseAddress `json:"address"` - Rawdata interface{} `json:"raw_data"` + Rawdata []byte `json:"raw_data"` Message []*NameValueType `json:"message"` Hash hexutil.Bytes `json:"hash"` Meta Metadata `json:"meta"` diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 92bf9db9d438..647cedffdc2c 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -122,7 +122,7 @@ var typedDataReferenceTypeRegexp = regexp.MustCompile(`^[A-Z](\w*)(\[\])?$`) // Note, the produced signature conforms to the secp256k1 curve R, S and V values, // where the V value will be 27 or 28 for legacy reasons. -func (api *SignerAPI) sign(ctx context.Context, addr common.MixedcaseAddress, req *SignDataRequest) (hexutil.Bytes, error) { +func (api *SignerAPI) sign(addr common.MixedcaseAddress, req *SignDataRequest) (hexutil.Bytes, error) { // We make the request prior to looking up if we actually have the account, to prevent // account-enumeration via the API @@ -158,7 +158,7 @@ func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr com return nil, err } - signature, err := api.sign(ctx, addr, req) + signature, err := api.sign(addr, req) if err != nil { api.UI.ShowError(err.Error()) return nil, err @@ -198,7 +198,7 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType Value: msg, }, } - req = &SignDataRequest{ContentType: mediaType, Rawdata: validatorData, Message: message, Hash: sighash} + req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Message: message, Hash: sighash} case ApplicationClique.Mime: // Clique is the Ethereum PoA standard cliqueData, err := hexutil.Decode(data.(string)) @@ -209,6 +209,8 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType if err := rlp.DecodeBytes(cliqueData, header); err != nil { return nil, err } + // Get back the rlp data, encoded by us + cliqueData = clique.CliqueRLP(header) sighash, err := SignCliqueHeader(header) if err != nil { return nil, err @@ -228,7 +230,7 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType if err != nil { return nil, err } - sighash, msg := SignTextPlain(plainData) + sighash, msg := accounts.TextAndHash(plainData) message := []*NameValueType{ { Name: "message", @@ -236,7 +238,7 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType Value: msg, }, } - req = &SignDataRequest{ContentType: mediaType, Rawdata: plainData, Message: message, Hash: sighash} + req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Message: message, Hash: sighash} } return req, nil @@ -263,40 +265,10 @@ func SignCliqueHeader(header *types.Header) (hexutil.Bytes, error) { if len(header.Extra) < 65 { return nil, fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) } - //hasher := sha3.NewLegacyKeccak256() hash := clique.SealHash(header) - //rlp.Encode(hasher, []interface{}{ - // header.ParentHash, - // header.UncleHash, - // header.Coinbase, - // header.Root, - // header.TxHash, - // header.ReceiptHash, - // header.Bloom, - // header.Difficulty, - // header.Number, - // header.GasLimit, - // header.GasUsed, - // header.Time, - // header.Extra[:len(header.Extra)-65], - // header.MixDigest, - // header.Nonce, - //}) - //hasher.Sum(hash[:0]) return hash.Bytes(), nil } -// 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 -// signing of transactions. -// hash = keccak256("\x19$Ethereum Signed Message:\n"${message length}${message}). -func SignTextPlain(data hexutil.Bytes) (hexutil.Bytes, string) { - // The letter `E` is \x45 in hex, retrofitting - // https://github.com/ethereum/go-ethereum/pull/2940/commits - msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data)) - return crypto.Keccak256([]byte(msg)), msg -} - // 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) { @@ -308,10 +280,11 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd if err != nil { return nil, err } - sighash := crypto.Keccak256([]byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))) + rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash))) + sighash := crypto.Keccak256(rawData) message := typedData.Format() - req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: typedData.Map(), Message: message, Hash: sighash} - signature, err := api.sign(ctx, addr, req) + req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: rawData, Message: message, Hash: sighash} + signature, err := api.sign(addr, req) if err != nil { api.UI.ShowError(err.Error()) return nil, err @@ -569,7 +542,7 @@ func (api *SignerAPI) EcRecover(ctx context.Context, data hexutil.Bytes, sig hex return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") } sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 - hash, _ := SignTextPlain(data) + hash := accounts.TextHash(data) rpk, err := crypto.SigToPub(hash, sig) if err != nil { return common.Address{}, err From 700e1d4e7fa98a9dd6a239eafe549d4ee042225e Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 5 Feb 2019 14:33:17 +0100 Subject: [PATCH 82/84] signer: prevent panic in type assertions, make cliui print rawdata as quotable-safe --- signer/core/cliui.go | 2 +- signer/core/signed_data.go | 29 ++++++++++++++++++++--------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/signer/core/cliui.go b/signer/core/cliui.go index 9c5250c4efb2..71d489d45967 100644 --- a/signer/core/cliui.go +++ b/signer/core/cliui.go @@ -169,7 +169,7 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResp fmt.Printf("%v\n", nvt.Pprint(1)) } //fmt.Printf("message: \n%v\n", request.Message) - fmt.Printf("raw data: \n%v\n", request.Rawdata) + fmt.Printf("raw data: \n%q\n", request.Rawdata) fmt.Printf("message hash: %v\n", request.Hash) fmt.Printf("-------------------------------------------\n") showMetadata(request.Meta) diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 647cedffdc2c..0d6ccbaa1b02 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -175,8 +175,6 @@ func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr com // This method returns the mimetype for signing along with the request func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (*SignDataRequest, error) { var req *SignDataRequest - req.Address = addr - req.Meta = MetadataFromContext(ctx) mediaType, _, err := mime.ParseMediaType(contentType) if err != nil { @@ -201,7 +199,11 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Message: message, Hash: sighash} case ApplicationClique.Mime: // Clique is the Ethereum PoA standard - cliqueData, err := hexutil.Decode(data.(string)) + stringData, ok := data.(string) + if !ok { + return nil, fmt.Errorf("input for %v plain must be an hex-encoded string", ApplicationClique.Mime) + } + cliqueData, err := hexutil.Decode(stringData) if err != nil { return nil, err } @@ -226,11 +228,16 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType default: // also case TextPlain.Mime: // Calculates an Ethereum ECDSA signature for: // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") - plainData, err := hexutil.Decode(data.(string)) - if err != nil { - return nil, err + // We expect it to be a string + stringData, ok := data.(string) + if !ok { + return nil, fmt.Errorf("input for text/plain must be a string") } - sighash, msg := accounts.TextAndHash(plainData) + //plainData, err := hexutil.Decode(stringdata) + //if err != nil { + // return nil, err + //} + sighash, msg := accounts.TextAndHash([]byte(stringData)) message := []*NameValueType{ { Name: "message", @@ -240,6 +247,8 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType } req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Message: message, Hash: sighash} } + req.Address = addr + req.Meta = MetadataFromContext(ctx) return req, nil } @@ -552,8 +561,10 @@ func (api *SignerAPI) EcRecover(ctx context.Context, data hexutil.Bytes, sig hex // UnmarshalValidatorData converts the bytes input to typed data func UnmarshalValidatorData(data interface{}) (ValidatorData, error) { - raw := data.(map[string]interface{}) - + raw, ok := data.(map[string]interface{}) + if !ok { + return ValidatorData{}, errors.New("validator input is not a map[string]interface{}") + } addr, ok := raw["address"].(string) if !ok { return ValidatorData{}, errors.New("validator address is not sent as a string") From c45f41cb6ec3426d30be4c34d6d0d9bd9a2e4b67 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 5 Feb 2019 15:06:37 +0100 Subject: [PATCH 83/84] signer: linter fixes, remove deprecated crypto dependency --- accounts/accounts.go | 6 ++++-- signer/core/signed_data.go | 2 +- signer/rules/rules_test.go | 5 ++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/accounts/accounts.go b/accounts/accounts.go index e8031e8b3b03..6cf50b98d0a8 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -19,12 +19,12 @@ package accounts import ( "fmt" + "golang.org/x/crypto/sha3" "math/big" ethereum "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/event" ) @@ -188,7 +188,9 @@ func TextHash(data []byte) []byte { // This gives context to the signed message and prevents signing of transactions. func TextAndHash(data []byte) ([]byte, string) { msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data)) - return crypto.Keccak256([]byte(msg)), msg + hasher := sha3.NewLegacyKeccak256() + hasher.Write([]byte(msg)) + return hasher.Sum(nil), msg } // WalletEventType represents the different event types that can be fired by diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 0d6ccbaa1b02..ac0b97bcad8e 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -21,7 +21,6 @@ import ( "context" "errors" "fmt" - "github.com/ethereum/go-ethereum/consensus/clique" "math/big" "mime" "regexp" @@ -35,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" diff --git a/signer/rules/rules_test.go b/signer/rules/rules_test.go index 2f5b0d593a90..d3b2edd5515f 100644 --- a/signer/rules/rules_test.go +++ b/signer/rules/rules_test.go @@ -637,8 +637,7 @@ function ApproveSignData(r){ return } message := "baz bazonk foo" - hash, _ := core.SignTextPlain([]byte(message)) - raw := hexutil.Bytes(message) + hash, rawdata := accounts.TextAndHash([]byte(message)) addr, _ := mixAddr("0x694267f14675d7e1b9494fd8d72fefe1755710fa") fmt.Printf("address %v %v\n", addr.String(), addr.Original()) @@ -655,7 +654,7 @@ function ApproveSignData(r){ Message: nvt, Hash: hash, Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"}, - Rawdata: raw, + Rawdata: []byte(rawdata), }) if err != nil { t.Fatalf("Unexpected error %v", err) From 622b03c7dd0300fb65d62ff7dbf306f077c8b269 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 5 Feb 2019 15:11:14 +0100 Subject: [PATCH 84/84] accounts: fix goimport --- accounts/accounts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accounts/accounts.go b/accounts/accounts.go index 6cf50b98d0a8..b57f282b32d6 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -19,13 +19,13 @@ package accounts import ( "fmt" - "golang.org/x/crypto/sha3" "math/big" ethereum "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" + "golang.org/x/crypto/sha3" ) // Account represents an Ethereum account located at a specific location defined