From 865e8e9f89dafb8a558e09351ebe443c58539639 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sun, 14 Oct 2018 19:57:28 +0100 Subject: [PATCH] Changed method to signTypedData --- cmd/clef/audit.log | 15 +++++ signer/core/api.go | 126 +++++++++++++++++++++----------------- signer/core/api_layout.md | 4 +- signer/core/apiv2.go | 32 ++-------- signer/core/auditlog.go | 10 +-- 5 files changed, 98 insertions(+), 89 deletions(-) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log index c5b542d9b0..d339f7e75c 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 3453dbacc8..8384790de6 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, } + TextValidator = SigFormat{ + "text/validator", + 0x01, + } + DataTyped = SigFormat{ + "data/typed", + 0x45, + } ApplicationClique = SigFormat{ "application/clique", - 0x01, - } - DataPlain = SigFormat{ - "data/plain", - 0x45, - } - DataStructured = SigFormat{ - "data/structured", - 0x46, + 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 bcb8c4cc5b..289f711eb9 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 4f3e737300..3485bcdf64 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 e80635ed02..c89c50f79e 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 }