2

I'm trying to generate a signature in line with https://eips.ethereum.org/EIPS/eip-712

so that I can permit spending of tokens without having to call Approve() first.

this is my code so far:

func GetPermitHashVars(permitTypeHash, domainSeparator [32]byte, owner, spender common.Address, amount, nonce, deadline *big.Int, pk *ecdsa.PrivateKey) (byte, [32]byte, [32]byte, error) {
// Calculate the permit hash
var message []byte
message = append(message, byte(0x19)) // EIP-191 header
message = append(message, byte(0x01)) // EIP-191 version

// Append the domainSeparator
for _, b := range domainSeparator {
    message = append(message, b)
}

// Append the permitTypeHash
for _, b := range permitTypeHash {
    message = append(message, b)
}

// Append the owner, spender, value, nonce, and deadline
message = append(message, owner.Bytes()...)
message = append(message, spender.Bytes()...)
message = append(message, amount.Bytes()...)
message = append(message, nonce.Bytes()...)
message = append(message, deadline.Bytes()...)

digest := crypto.Keccak256(message)

sig, err := crypto.Sign(digest, pk)
if err != nil {
    return 0, [32]byte{}, [32]byte{}, err
}

var v byte
var r [32]byte
var s [32]byte

v = sig[64] + 27
copy(r[:], sig[:32])
copy(s[:], sig[32:64])

return v, r, s, nil
}

I keep getting an invalid signature error from the contract and I can't work out why, any ideas would be much appreciated!

UPDATE:

I've edited the code to try a different approach, but same error! any ideas? i'm really lost on this one.

func GetPermitHashVars(name string, owner, spender, pair common.Address, amount, nonce, deadline, chainId *big.Int, pk *ecdsa.PrivateKey) (byte, [32]byte, [32]byte, error) {
var typesStandard = apitypes.Types{
    "EIP712Domain": {
        {
            Name: "name",
            Type: "string",
        },
        {
            Name: "version",
            Type: "string",
        },
        {
            Name: "chainId",
            Type: "uint256",
        },
        {
            Name: "verifyingContract",
            Type: "address",
        },
    },
    "Permit": {
        {
            Name: "owner",
            Type: "address",
        },
        {
            Name: "spender",
            Type: "address",
        },
        {
            Name: "value",
            Type: "uint256",
        },
        {
            Name: "nonce",
            Type: "uint256",
        },
        {
            Name: "deadline",
            Type: "uint256",
        },
    },
}

var domainStandard = apitypes.TypedDataDomain{
    Name:              name,
    Version:           "1",
    ChainId:           math.NewHexOrDecimal256(chainId.Int64()),
    VerifyingContract: pair.Hex(),
}

typedData := apitypes.TypedData{
    Domain:      domainStandard,
    PrimaryType: "Permit",
    Types:       typesStandard,
    Message:     apitypes.TypedDataMessage{"owner": owner.Hex(), "spender": spender.Hex(), "value": amount, "nonce": nonce, "deadline": deadline},
}

domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map())
if err != nil {
    return 0, [32]byte{}, [32]byte{}, err
}

typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
if err != nil {
    return 0, [32]byte{}, [32]byte{}, err
}

rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))

hashed := crypto.Keccak256(rawData)

sig, err := crypto.Sign(hashed, pk)
if err != nil {
    return 0, [32]byte{}, [32]byte{}, err
}

fmt.Println(len(sig))

var v byte
var r [32]byte
var s [32]byte

v = sig[64] + 27
copy(r[:], sig[:32])
copy(s[:], sig[32:64])

return v, r, s, nil
}
1

1 Answer 1

1

Solved it myself in the end, my main confusion was in the difference between abi.encode & abi.encodePacked (not sure go-ethereum doesn't have a version of these funcs like other ethereum packages do). Here's my solution.

// get v, r, s variables from eip712 signature for permit function
func GetPermitHashVars(domainSeparator, permitHash [32]byte, owner, spender 
common.Address, value, nonce, deadline *big.Int, pk *ecdsa.PrivateKey) (byte,    
[32]byte, [32]byte, error) {

uint256Ty, _ := abi.NewType("uint256", "uint256", nil)
bytes32Ty, _ := abi.NewType("bytes32", "bytes32", nil)
addressTy, _ := abi.NewType("address", "address", nil)

args := abi.Arguments{
    {
        Type: bytes32Ty,
    },
    {
        Type: addressTy,
    },
    {
        Type: addressTy,
    },
    {
        Type: uint256Ty,
    },
    {
        Type: uint256Ty,
    },
    {
        Type: uint256Ty,
    },
}

//abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonce, deadline)
bytes, err := args.Pack(
    permitHash,
    owner,
    spender,
    value,
    nonce,
    deadline,
)
if err != nil {
    return 0, [32]byte{}, [32]byte{}, err
}

//this is eq to keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonce, deadline))
typedHash := crypto.Keccak256(bytes)

//this is eq to keccak256(abi.encodePacked('x19x01', DOMAIN_SEPARATOR, hashed_args)) 
hash := crypto.Keccak256(
    []byte("\x19\x01"),
    domainSeparator[:],
    typedHash,
)

sig, err := crypto.Sign(hash, pk)
if err != nil {
    return 0, [32]byte{}, [32]byte{}, err
}

var v byte
var r [32]byte
var s [32]byte

v = sig[64] + 27
copy(r[:], sig[:32])
copy(s[:], sig[32:64])

return v, r, s, nil

}

1
  • Welcome to the Ethereum Stack Exchange and thanks for sharing!
    – eth
    Aug 9, 2023 at 7:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.