I am trying to recover the signer of a TypedData message (eip712).
I am able to sign and verify with ethersJS and validate in the contract as well.
With Go, I can sign and verify as well but the verification fail in the contract.
Turn out this code is working fine.
The TypedData;
signerData := TypedData{
Types: Types{
"Quote": []Type{
{Name: "cost", Type: "uint256"},
{Name: "deadline", Type: "uint256"},
},
"EIP712Domain": []Type{
{Name: "name", Type: "string"},
{Name: "chainId", Type: "uint256"},
{Name: "version", Type: "string"},
{Name: "verifyingContract", Type: "string"},
{Name: "salt", Type: "string"},
},
},
PrimaryType: "Quote",
Domain: TypedDataDomain{
Name: "MyDomain",
Version: "1",
ChainId: ethMath.NewHexOrDecimal256(ChainId.Int64()),
VerifyingContract: contractAddress.String(),
Salt: "0",
},
Message: TypedDataMessage{
"cost": "0",
"deadline": "1659095005",
},
}
Encoding the data;
func EncodeForSigning(typedData TypedData) (hash common.Hash, err error) {
domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map())
if err != nil {
return
}
typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
if err != nil {
return
}
rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))
hash = common.BytesToHash(crypto.Keccak256(rawData))
return
}
Sign TypedData;
func (s Signer) SignTypedData(typedData TypedData) (sig []byte, err error) {
hash, err := EncodeForSigning(typedData)
if err != nil {
return
}
sig, err = crypto.Sign(hash.Bytes(), s.key)
if err != nil {
return
}
sig[64] += 27
return
}
I am able to verify it in Go;
func Ecrecover(hash, signature []byte) (addr common.Address, err error) {
sig := make([]byte, len(signature))
copy(sig, signature)
if len(sig) != 65 {
err = fmt.Errorf("invalid length of signture: %d", len(sig))
return
}
if sig[64] != 27 && sig[64] != 28 && sig[64] != 1 && sig[64] != 0 {
err = fmt.Errorf("invalid signature type")
return
}
if sig[64] >= 27 {
sig[64] -= 27
}
recoverPub, err := crypto.Ecrecover(hash, sig)
if err != nil {
err = fmt.Errorf("can not ecrecover: %v", err)
return
}
pubKey, err := crypto.UnmarshalPubkey(recoverPub)
if err != nil {
err = fmt.Errorf("can not unmarshal pubkey: %v", err)
return
}
addr = crypto.PubkeyToAddress(*pubKey)
return
}
However in the contract it fails, I have tried with ECDSA and _hashTypedDataV4, they both return the same address but it is wrong.
function validateQuote(uint256 _cost,uint256 _deadline, bytes memory _signature) public view returns (address) {
Quote memory quote = Quote({
cost: _cost,
deadline: _deadline
});
bytes32 msghash = _hashTypedDataV4(hash(quote));
uint8 v;
bytes32 r;
bytes32 s;
(v, r, s) = splitSignature(_signature);
(address signer, ) = ECDSA.tryRecover(msghash,_signature);
console.log("v4 signer", signer);
console.log("v", v);
return (verifyQuote(quote, v,r,s));
}
The Hexadecimal representation of the signature is the same in go and in solidity, however the signer returned by solidity is not valid.
V = 27
The go validation return the correct address
//validate signature
hash, err = EncodeForSigning(signerData)
signerAddress, err := Ecrecover(hash.Bytes(), sig)
fmt.Println("Signer Recovery", signerAddress) // good
What would be the cause of the failure on the contract side?
I was able to validate that same signature with Ethers where V=28 The same signature with go set's V to 27. Not sure it's related.