github.com/hardtosaygoodbye/go-ethereum@v1.10.16-0.20220122011429-97003b9e6c15/signer/core/signed_data.go (about) 1 // Copyright 2019 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package core 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "mime" 24 25 "github.com/hardtosaygoodbye/go-ethereum/accounts" 26 "github.com/hardtosaygoodbye/go-ethereum/common" 27 "github.com/hardtosaygoodbye/go-ethereum/common/hexutil" 28 "github.com/hardtosaygoodbye/go-ethereum/consensus/clique" 29 "github.com/hardtosaygoodbye/go-ethereum/core/types" 30 "github.com/hardtosaygoodbye/go-ethereum/crypto" 31 "github.com/hardtosaygoodbye/go-ethereum/rlp" 32 "github.com/hardtosaygoodbye/go-ethereum/signer/core/apitypes" 33 ) 34 35 // sign receives a request and produces a signature 36 // 37 // Note, the produced signature conforms to the secp256k1 curve R, S and V values, 38 // where the V value will be 27 or 28 for legacy reasons, if legacyV==true. 39 func (api *SignerAPI) sign(req *SignDataRequest, legacyV bool) (hexutil.Bytes, error) { 40 // We make the request prior to looking up if we actually have the account, to prevent 41 // account-enumeration via the API 42 res, err := api.UI.ApproveSignData(req) 43 if err != nil { 44 return nil, err 45 } 46 if !res.Approved { 47 return nil, ErrRequestDenied 48 } 49 // Look up the wallet containing the requested signer 50 account := accounts.Account{Address: req.Address.Address()} 51 wallet, err := api.am.Find(account) 52 if err != nil { 53 return nil, err 54 } 55 pw, err := api.lookupOrQueryPassword(account.Address, 56 "Password for signing", 57 fmt.Sprintf("Please enter password for signing data with account %s", account.Address.Hex())) 58 if err != nil { 59 return nil, err 60 } 61 // Sign the data with the wallet 62 signature, err := wallet.SignDataWithPassphrase(account, pw, req.ContentType, req.Rawdata) 63 if err != nil { 64 return nil, err 65 } 66 if legacyV { 67 signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper 68 } 69 return signature, nil 70 } 71 72 // SignData signs the hash of the provided data, but does so differently 73 // depending on the content-type specified. 74 // 75 // Different types of validation occur. 76 func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error) { 77 var req, transformV, err = api.determineSignatureFormat(ctx, contentType, addr, data) 78 if err != nil { 79 return nil, err 80 } 81 signature, err := api.sign(req, transformV) 82 if err != nil { 83 api.UI.ShowError(err.Error()) 84 return nil, err 85 } 86 return signature, nil 87 } 88 89 // determineSignatureFormat determines which signature method should be used based upon the mime type 90 // In the cases where it matters ensure that the charset is handled. The charset 91 // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType 92 // charset, ok := params["charset"] 93 // As it is now, we accept any charset and just treat it as 'raw'. 94 // This method returns the mimetype for signing along with the request 95 func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (*SignDataRequest, bool, error) { 96 var ( 97 req *SignDataRequest 98 useEthereumV = true // Default to use V = 27 or 28, the legacy Ethereum format 99 ) 100 mediaType, _, err := mime.ParseMediaType(contentType) 101 if err != nil { 102 return nil, useEthereumV, err 103 } 104 105 switch mediaType { 106 case apitypes.IntendedValidator.Mime: 107 // Data with an intended validator 108 validatorData, err := UnmarshalValidatorData(data) 109 if err != nil { 110 return nil, useEthereumV, err 111 } 112 sighash, msg := SignTextValidator(validatorData) 113 messages := []*apitypes.NameValueType{ 114 { 115 Name: "This is a request to sign data intended for a particular validator (see EIP 191 version 0)", 116 Typ: "description", 117 Value: "", 118 }, 119 { 120 Name: "Intended validator address", 121 Typ: "address", 122 Value: validatorData.Address.String(), 123 }, 124 { 125 Name: "Application-specific data", 126 Typ: "hexdata", 127 Value: validatorData.Message, 128 }, 129 { 130 Name: "Full message for signing", 131 Typ: "hexdata", 132 Value: fmt.Sprintf("0x%x", msg), 133 }, 134 } 135 req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Messages: messages, Hash: sighash} 136 case apitypes.ApplicationClique.Mime: 137 // Clique is the Ethereum PoA standard 138 stringData, ok := data.(string) 139 if !ok { 140 return nil, useEthereumV, fmt.Errorf("input for %v must be an hex-encoded string", apitypes.ApplicationClique.Mime) 141 } 142 cliqueData, err := hexutil.Decode(stringData) 143 if err != nil { 144 return nil, useEthereumV, err 145 } 146 header := &types.Header{} 147 if err := rlp.DecodeBytes(cliqueData, header); err != nil { 148 return nil, useEthereumV, err 149 } 150 // The incoming clique header is already truncated, sent to us with a extradata already shortened 151 if len(header.Extra) < 65 { 152 // Need to add it back, to get a suitable length for hashing 153 newExtra := make([]byte, len(header.Extra)+65) 154 copy(newExtra, header.Extra) 155 header.Extra = newExtra 156 } 157 // Get back the rlp data, encoded by us 158 sighash, cliqueRlp, err := cliqueHeaderHashAndRlp(header) 159 if err != nil { 160 return nil, useEthereumV, err 161 } 162 messages := []*apitypes.NameValueType{ 163 { 164 Name: "Clique header", 165 Typ: "clique", 166 Value: fmt.Sprintf("clique header %d [0x%x]", header.Number, header.Hash()), 167 }, 168 } 169 // Clique uses V on the form 0 or 1 170 useEthereumV = false 171 req = &SignDataRequest{ContentType: mediaType, Rawdata: cliqueRlp, Messages: messages, Hash: sighash} 172 default: // also case TextPlain.Mime: 173 // Calculates an Ethereum ECDSA signature for: 174 // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") 175 // We expect it to be a string 176 if stringData, ok := data.(string); !ok { 177 return nil, useEthereumV, fmt.Errorf("input for text/plain must be an hex-encoded string") 178 } else { 179 if textData, err := hexutil.Decode(stringData); err != nil { 180 return nil, useEthereumV, err 181 } else { 182 sighash, msg := accounts.TextAndHash(textData) 183 messages := []*apitypes.NameValueType{ 184 { 185 Name: "message", 186 Typ: accounts.MimetypeTextPlain, 187 Value: msg, 188 }, 189 } 190 req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Messages: messages, Hash: sighash} 191 } 192 } 193 } 194 req.Address = addr 195 req.Meta = MetadataFromContext(ctx) 196 return req, useEthereumV, nil 197 } 198 199 // SignTextWithValidator signs the given message which can be further recovered 200 // with the given validator. 201 // hash = keccak256("\x19\x00"${address}${data}). 202 func SignTextValidator(validatorData apitypes.ValidatorData) (hexutil.Bytes, string) { 203 msg := fmt.Sprintf("\x19\x00%s%s", string(validatorData.Address.Bytes()), string(validatorData.Message)) 204 return crypto.Keccak256([]byte(msg)), msg 205 } 206 207 // cliqueHeaderHashAndRlp returns the hash which is used as input for the proof-of-authority 208 // signing. It is the hash of the entire header apart from the 65 byte signature 209 // contained at the end of the extra data. 210 // 211 // The method requires the extra data to be at least 65 bytes -- the original implementation 212 // in clique.go panics if this is the case, thus it's been reimplemented here to avoid the panic 213 // and simply return an error instead 214 func cliqueHeaderHashAndRlp(header *types.Header) (hash, rlp []byte, err error) { 215 if len(header.Extra) < 65 { 216 err = fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) 217 return 218 } 219 rlp = clique.CliqueRLP(header) 220 hash = clique.SealHash(header).Bytes() 221 return hash, rlp, err 222 } 223 224 // SignTypedData signs EIP-712 conformant typed data 225 // hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}") 226 // It returns 227 // - the signature, 228 // - and/or any error 229 func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, typedData apitypes.TypedData) (hexutil.Bytes, error) { 230 signature, _, err := api.signTypedData(ctx, addr, typedData, nil) 231 return signature, err 232 } 233 234 // signTypedData is identical to the capitalized version, except that it also returns the hash (preimage) 235 // - the signature preimage (hash) 236 func (api *SignerAPI) signTypedData(ctx context.Context, addr common.MixedcaseAddress, 237 typedData apitypes.TypedData, validationMessages *apitypes.ValidationMessages) (hexutil.Bytes, hexutil.Bytes, error) { 238 domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) 239 if err != nil { 240 return nil, nil, err 241 } 242 typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) 243 if err != nil { 244 return nil, nil, err 245 } 246 rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash))) 247 sighash := crypto.Keccak256(rawData) 248 messages, err := typedData.Format() 249 if err != nil { 250 return nil, nil, err 251 } 252 req := &SignDataRequest{ 253 ContentType: apitypes.DataTyped.Mime, 254 Rawdata: rawData, 255 Messages: messages, 256 Hash: sighash, 257 Address: addr} 258 if validationMessages != nil { 259 req.Callinfo = validationMessages.Messages 260 } 261 signature, err := api.sign(req, true) 262 if err != nil { 263 api.UI.ShowError(err.Error()) 264 return nil, nil, err 265 } 266 return signature, sighash, nil 267 } 268 269 // EcRecover recovers the address associated with the given sig. 270 // Only compatible with `text/plain` 271 func (api *SignerAPI) EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { 272 // Returns the address for the Account that was used to create the signature. 273 // 274 // Note, this function is compatible with eth_sign and personal_sign. As such it recovers 275 // the address of: 276 // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") 277 // addr = ecrecover(hash, signature) 278 // 279 // Note, the signature must conform to the secp256k1 curve R, S and V values, where 280 // the V value must be be 27 or 28 for legacy reasons. 281 // 282 // https://github.com/hardtosaygoodbye/go-ethereum/wiki/Management-APIs#personal_ecRecover 283 if len(sig) != 65 { 284 return common.Address{}, fmt.Errorf("signature must be 65 bytes long") 285 } 286 if sig[64] != 27 && sig[64] != 28 { 287 return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") 288 } 289 sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 290 hash := accounts.TextHash(data) 291 rpk, err := crypto.SigToPub(hash, sig) 292 if err != nil { 293 return common.Address{}, err 294 } 295 return crypto.PubkeyToAddress(*rpk), nil 296 } 297 298 // UnmarshalValidatorData converts the bytes input to typed data 299 func UnmarshalValidatorData(data interface{}) (apitypes.ValidatorData, error) { 300 raw, ok := data.(map[string]interface{}) 301 if !ok { 302 return apitypes.ValidatorData{}, errors.New("validator input is not a map[string]interface{}") 303 } 304 addr, ok := raw["address"].(string) 305 if !ok { 306 return apitypes.ValidatorData{}, errors.New("validator address is not sent as a string") 307 } 308 addrBytes, err := hexutil.Decode(addr) 309 if err != nil { 310 return apitypes.ValidatorData{}, err 311 } 312 if !ok || len(addrBytes) == 0 { 313 return apitypes.ValidatorData{}, errors.New("validator address is undefined") 314 } 315 316 message, ok := raw["message"].(string) 317 if !ok { 318 return apitypes.ValidatorData{}, errors.New("message is not sent as a string") 319 } 320 messageBytes, err := hexutil.Decode(message) 321 if err != nil { 322 return apitypes.ValidatorData{}, err 323 } 324 if !ok || len(messageBytes) == 0 { 325 return apitypes.ValidatorData{}, errors.New("message is undefined") 326 } 327 328 return apitypes.ValidatorData{ 329 Address: common.BytesToAddress(addrBytes), 330 Message: messageBytes, 331 }, nil 332 }