From 6910ba299137ec569fc1920fcbbfba7e24e56857 Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Thu, 25 Oct 2018 14:41:04 +0200 Subject: [PATCH] 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 652dc04ae8..45814ac8cf 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 9795a8d3c6..548438d355 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"`