github.com/theQRL/go-zond@v0.1.1/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 "encoding/json" 22 "errors" 23 "fmt" 24 "mime" 25 26 "github.com/theQRL/go-zond/accounts" 27 "github.com/theQRL/go-zond/common" 28 "github.com/theQRL/go-zond/common/hexutil" 29 "github.com/theQRL/go-zond/consensus/clique" 30 "github.com/theQRL/go-zond/core/types" 31 "github.com/theQRL/go-zond/crypto" 32 "github.com/theQRL/go-zond/rlp" 33 "github.com/theQRL/go-zond/signer/core/apitypes" 34 ) 35 36 // sign receives a request and produces a signature 37 // 38 // Note, the produced signature conforms to the secp256k1 curve R, S and V values, 39 // where the V value will be 27 or 28 for legacy reasons, if legacyV==true. 40 func (api *SignerAPI) sign(req *SignDataRequest, legacyV bool) (hexutil.Bytes, error) { 41 // We make the request prior to looking up if we actually have the account, to prevent 42 // account-enumeration via the API 43 res, err := api.UI.ApproveSignData(req) 44 if err != nil { 45 return nil, err 46 } 47 if !res.Approved { 48 return nil, ErrRequestDenied 49 } 50 // Look up the wallet containing the requested signer 51 account := accounts.Account{Address: req.Address.Address()} 52 wallet, err := api.am.Find(account) 53 if err != nil { 54 return nil, err 55 } 56 pw, err := api.lookupOrQueryPassword(account.Address, 57 "Password for signing", 58 fmt.Sprintf("Please enter password for signing data with account %s", account.Address.Hex())) 59 if err != nil { 60 return nil, err 61 } 62 // Sign the data with the wallet 63 signature, err := wallet.SignDataWithPassphrase(account, pw, req.ContentType, req.Rawdata) 64 if err != nil { 65 return nil, err 66 } 67 if legacyV { 68 signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper 69 } 70 return signature, nil 71 } 72 73 // SignData signs the hash of the provided data, but does so differently 74 // depending on the content-type specified. 75 // 76 // Different types of validation occur. 77 func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error) { 78 var req, transformV, err = api.determineSignatureFormat(ctx, contentType, addr, data) 79 if err != nil { 80 return nil, err 81 } 82 signature, err := api.sign(req, transformV) 83 if err != nil { 84 api.UI.ShowError(err.Error()) 85 return nil, err 86 } 87 return signature, nil 88 } 89 90 // determineSignatureFormat determines which signature method should be used based upon the mime type 91 // In the cases where it matters ensure that the charset is handled. The charset 92 // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType 93 // charset, ok := params["charset"] 94 // As it is now, we accept any charset and just treat it as 'raw'. 95 // This method returns the mimetype for signing along with the request 96 func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (*SignDataRequest, bool, error) { 97 var ( 98 req *SignDataRequest 99 useEthereumV = true // Default to use V = 27 or 28, the legacy Ethereum format 100 ) 101 mediaType, _, err := mime.ParseMediaType(contentType) 102 if err != nil { 103 return nil, useEthereumV, err 104 } 105 106 switch mediaType { 107 case apitypes.IntendedValidator.Mime: 108 // Data with an intended validator 109 validatorData, err := UnmarshalValidatorData(data) 110 if err != nil { 111 return nil, useEthereumV, err 112 } 113 sighash, msg := SignTextValidator(validatorData) 114 messages := []*apitypes.NameValueType{ 115 { 116 Name: "This is a request to sign data intended for a particular validator (see EIP 191 version 0)", 117 Typ: "description", 118 Value: "", 119 }, 120 { 121 Name: "Intended validator address", 122 Typ: "address", 123 Value: validatorData.Address.String(), 124 }, 125 { 126 Name: "Application-specific data", 127 Typ: "hexdata", 128 Value: validatorData.Message, 129 }, 130 { 131 Name: "Full message for signing", 132 Typ: "hexdata", 133 Value: fmt.Sprintf("%#x", msg), 134 }, 135 } 136 req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Messages: messages, Hash: sighash} 137 case apitypes.ApplicationClique.Mime: 138 // Clique is the Ethereum PoA standard 139 cliqueData, err := fromHex(data) 140 if err != nil { 141 return nil, useEthereumV, err 142 } 143 header := &types.Header{} 144 if err := rlp.DecodeBytes(cliqueData, header); err != nil { 145 return nil, useEthereumV, err 146 } 147 // Add space in the extradata to put the signature 148 newExtra := make([]byte, len(header.Extra)+65) 149 copy(newExtra, header.Extra) 150 header.Extra = newExtra 151 152 // Get back the rlp data, encoded by us 153 sighash, cliqueRlp, err := cliqueHeaderHashAndRlp(header) 154 if err != nil { 155 return nil, useEthereumV, err 156 } 157 messages := []*apitypes.NameValueType{ 158 { 159 Name: "Clique header", 160 Typ: "clique", 161 Value: fmt.Sprintf("clique header %d [%#x]", header.Number, header.Hash()), 162 }, 163 } 164 // Clique uses V on the form 0 or 1 165 useEthereumV = false 166 req = &SignDataRequest{ContentType: mediaType, Rawdata: cliqueRlp, Messages: messages, Hash: sighash} 167 case apitypes.DataTyped.Mime: 168 // EIP-712 conformant typed data 169 var err error 170 req, err = typedDataRequest(data) 171 if err != nil { 172 return nil, useEthereumV, err 173 } 174 default: // also case TextPlain.Mime: 175 // Calculates an Ethereum ECDSA signature for: 176 // hash = keccak256("\x19Ethereum Signed Message:\n${message length}${message}") 177 // We expect input to be a hex-encoded string 178 textData, err := fromHex(data) 179 if err != nil { 180 return nil, useEthereumV, err 181 } 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 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 req, err := typedDataRequest(typedData) 237 if err != nil { 238 return nil, nil, err 239 } 240 req.Address = addr 241 req.Meta = MetadataFromContext(ctx) 242 if validationMessages != nil { 243 req.Callinfo = validationMessages.Messages 244 } 245 signature, err := api.sign(req, true) 246 if err != nil { 247 api.UI.ShowError(err.Error()) 248 return nil, nil, err 249 } 250 return signature, req.Hash, nil 251 } 252 253 // fromHex tries to interpret the data as type string, and convert from 254 // hexadecimal to []byte 255 func fromHex(data any) ([]byte, error) { 256 if stringData, ok := data.(string); ok { 257 binary, err := hexutil.Decode(stringData) 258 return binary, err 259 } 260 return nil, fmt.Errorf("wrong type %T", data) 261 } 262 263 // typeDataRequest tries to convert the data into a SignDataRequest. 264 func typedDataRequest(data any) (*SignDataRequest, error) { 265 var typedData apitypes.TypedData 266 if td, ok := data.(apitypes.TypedData); ok { 267 typedData = td 268 } else { // Hex-encoded data 269 jsonData, err := fromHex(data) 270 if err != nil { 271 return nil, err 272 } 273 if err = json.Unmarshal(jsonData, &typedData); err != nil { 274 return nil, err 275 } 276 } 277 messages, err := typedData.Format() 278 if err != nil { 279 return nil, err 280 } 281 sighash, rawData, err := apitypes.TypedDataAndHash(typedData) 282 if err != nil { 283 return nil, err 284 } 285 return &SignDataRequest{ 286 ContentType: apitypes.DataTyped.Mime, 287 Rawdata: []byte(rawData), 288 Messages: messages, 289 Hash: sighash}, nil 290 } 291 292 // EcRecover recovers the address associated with the given sig. 293 // Only compatible with `text/plain` 294 func (api *SignerAPI) EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { 295 // Returns the address for the Account that was used to create the signature. 296 // 297 // Note, this function is compatible with zond_sign and personal_sign. As such it recovers 298 // the address of: 299 // hash = keccak256("\x19Ethereum Signed Message:\n${message length}${message}") 300 // addr = ecrecover(hash, signature) 301 // 302 // Note, the signature must conform to the secp256k1 curve R, S and V values, where 303 // the V value must be 27 or 28 for legacy reasons. 304 // 305 // https://github.com/theQRL/go-zond/wiki/Management-APIs#personal_ecRecover 306 if len(sig) != 65 { 307 return common.Address{}, errors.New("signature must be 65 bytes long") 308 } 309 if sig[64] != 27 && sig[64] != 28 { 310 return common.Address{}, errors.New("invalid Ethereum signature (V is not 27 or 28)") 311 } 312 sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 313 hash := accounts.TextHash(data) 314 rpk, err := crypto.SigToPub(hash, sig) 315 if err != nil { 316 return common.Address{}, err 317 } 318 return crypto.PubkeyToAddress(*rpk), nil 319 } 320 321 // UnmarshalValidatorData converts the bytes input to typed data 322 func UnmarshalValidatorData(data interface{}) (apitypes.ValidatorData, error) { 323 raw, ok := data.(map[string]interface{}) 324 if !ok { 325 return apitypes.ValidatorData{}, errors.New("validator input is not a map[string]interface{}") 326 } 327 addrBytes, err := fromHex(raw["address"]) 328 if err != nil { 329 return apitypes.ValidatorData{}, fmt.Errorf("validator address error: %w", err) 330 } 331 if len(addrBytes) == 0 { 332 return apitypes.ValidatorData{}, errors.New("validator address is undefined") 333 } 334 messageBytes, err := fromHex(raw["message"]) 335 if err != nil { 336 return apitypes.ValidatorData{}, fmt.Errorf("message error: %w", err) 337 } 338 if len(messageBytes) == 0 { 339 return apitypes.ValidatorData{}, errors.New("message is undefined") 340 } 341 return apitypes.ValidatorData{ 342 Address: common.BytesToAddress(addrBytes), 343 Message: messageBytes, 344 }, nil 345 }