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
    Commented Aug 9, 2023 at 7:22

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