github.com/carter-ya/go-ethereum@v0.0.0-20230628080049-d2309be3983b/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/ethereum/go-ethereum/accounts" 26 "github.com/ethereum/go-ethereum/common" 27 "github.com/ethereum/go-ethereum/common/hexutil" 28 "github.com/ethereum/go-ethereum/consensus/clique" 29 "github.com/ethereum/go-ethereum/core/types" 30 "github.com/ethereum/go-ethereum/crypto" 31 "github.com/ethereum/go-ethereum/rlp" 32 "github.com/ethereum/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("%#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 // Add space in the extradata to put the signature 151 newExtra := make([]byte, len(header.Extra)+65) 152 copy(newExtra, header.Extra) 153 header.Extra = newExtra 154 155 // Get back the rlp data, encoded by us 156 sighash, cliqueRlp, err := cliqueHeaderHashAndRlp(header) 157 if err != nil { 158 return nil, useEthereumV, err 159 } 160 messages := []*apitypes.NameValueType{ 161 { 162 Name: "Clique header", 163 Typ: "clique", 164 Value: fmt.Sprintf("clique header %d [%#x]", header.Number, header.Hash()), 165 }, 166 } 167 // Clique uses V on the form 0 or 1 168 useEthereumV = false 169 req = &SignDataRequest{ContentType: mediaType, Rawdata: cliqueRlp, Messages: messages, Hash: sighash} 170 default: // also case TextPlain.Mime: 171 // Calculates an Ethereum ECDSA signature for: 172 // hash = keccak256("\x19Ethereum Signed Message:\n${message length}${message}") 173 // We expect it to be a string 174 if stringData, ok := data.(string); !ok { 175 return nil, useEthereumV, fmt.Errorf("input for text/plain must be an hex-encoded string") 176 } else { 177 if textData, err := hexutil.Decode(stringData); err != nil { 178 return nil, useEthereumV, err 179 } else { 180 sighash, msg := accounts.TextAndHash(textData) 181 messages := []*apitypes.NameValueType{ 182 { 183 Name: "message", 184 Typ: accounts.MimetypeTextPlain, 185 Value: msg, 186 }, 187 } 188 req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Messages: messages, Hash: sighash} 189 } 190 } 191 } 192 req.Address = addr 193 req.Meta = MetadataFromContext(ctx) 194 return req, useEthereumV, nil 195 } 196 197 // SignTextValidator signs the given message which can be further recovered 198 // with the given validator. 199 // hash = keccak256("\x19\x00"${address}${data}). 200 func SignTextValidator(validatorData apitypes.ValidatorData) (hexutil.Bytes, string) { 201 msg := fmt.Sprintf("\x19\x00%s%s", string(validatorData.Address.Bytes()), string(validatorData.Message)) 202 return crypto.Keccak256([]byte(msg)), msg 203 } 204 205 // cliqueHeaderHashAndRlp returns the hash which is used as input for the proof-of-authority 206 // signing. It is the hash of the entire header apart from the 65 byte signature 207 // contained at the end of the extra data. 208 // 209 // The method requires the extra data to be at least 65 bytes -- the original implementation 210 // in clique.go panics if this is the case, thus it's been reimplemented here to avoid the panic 211 // and simply return an error instead 212 func cliqueHeaderHashAndRlp(header *types.Header) (hash, rlp []byte, err error) { 213 if len(header.Extra) < 65 { 214 err = fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) 215 return 216 } 217 rlp = clique.CliqueRLP(header) 218 hash = clique.SealHash(header).Bytes() 219 return hash, rlp, err 220 } 221 222 // SignTypedData signs EIP-712 conformant typed data 223 // hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}") 224 // It returns 225 // - the signature, 226 // - and/or any error 227 func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, typedData apitypes.TypedData) (hexutil.Bytes, error) { 228 signature, _, err := api.signTypedData(ctx, addr, typedData, nil) 229 return signature, err 230 } 231 232 // signTypedData is identical to the capitalized version, except that it also returns the hash (preimage) 233 // - the signature preimage (hash) 234 func (api *SignerAPI) signTypedData(ctx context.Context, addr common.MixedcaseAddress, 235 typedData apitypes.TypedData, validationMessages *apitypes.ValidationMessages) (hexutil.Bytes, hexutil.Bytes, error) { 236 sighash, rawData, err := apitypes.TypedDataAndHash(typedData) 237 if err != nil { 238 return nil, nil, err 239 } 240 messages, err := typedData.Format() 241 if err != nil { 242 return nil, nil, err 243 } 244 req := &SignDataRequest{ 245 ContentType: apitypes.DataTyped.Mime, 246 Rawdata: []byte(rawData), 247 Messages: messages, 248 Hash: sighash, 249 Address: addr} 250 if validationMessages != nil { 251 req.Callinfo = validationMessages.Messages 252 } 253 signature, err := api.sign(req, true) 254 if err != nil { 255 api.UI.ShowError(err.Error()) 256 return nil, nil, err 257 } 258 return signature, sighash, nil 259 } 260 261 // EcRecover recovers the address associated with the given sig. 262 // Only compatible with `text/plain` 263 func (api *SignerAPI) EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { 264 // Returns the address for the Account that was used to create the signature. 265 // 266 // Note, this function is compatible with eth_sign and personal_sign. As such it recovers 267 // the address of: 268 // hash = keccak256("\x19Ethereum Signed Message:\n${message length}${message}") 269 // addr = ecrecover(hash, signature) 270 // 271 // Note, the signature must conform to the secp256k1 curve R, S and V values, where 272 // the V value must be 27 or 28 for legacy reasons. 273 // 274 // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover 275 if len(sig) != 65 { 276 return common.Address{}, fmt.Errorf("signature must be 65 bytes long") 277 } 278 if sig[64] != 27 && sig[64] != 28 { 279 return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") 280 } 281 sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 282 hash := accounts.TextHash(data) 283 rpk, err := crypto.SigToPub(hash, sig) 284 if err != nil { 285 return common.Address{}, err 286 } 287 return crypto.PubkeyToAddress(*rpk), nil 288 } 289 290 // UnmarshalValidatorData converts the bytes input to typed data 291 func UnmarshalValidatorData(data interface{}) (apitypes.ValidatorData, error) { 292 raw, ok := data.(map[string]interface{}) 293 if !ok { 294 return apitypes.ValidatorData{}, errors.New("validator input is not a map[string]interface{}") 295 } 296 addr, ok := raw["address"].(string) 297 if !ok { 298 return apitypes.ValidatorData{}, errors.New("validator address is not sent as a string") 299 } 300 addrBytes, err := hexutil.Decode(addr) 301 if err != nil { 302 return apitypes.ValidatorData{}, err 303 } 304 if !ok || len(addrBytes) == 0 { 305 return apitypes.ValidatorData{}, errors.New("validator address is undefined") 306 } 307 308 message, ok := raw["message"].(string) 309 if !ok { 310 return apitypes.ValidatorData{}, errors.New("message is not sent as a string") 311 } 312 messageBytes, err := hexutil.Decode(message) 313 if err != nil { 314 return apitypes.ValidatorData{}, err 315 } 316 if !ok || len(messageBytes) == 0 { 317 return apitypes.ValidatorData{}, errors.New("message is undefined") 318 } 319 320 return apitypes.ValidatorData{ 321 Address: common.BytesToAddress(addrBytes), 322 Message: messageBytes, 323 }, nil 324 }