Skip to content

Commit

Permalink
intents: add eip712 support via new IntentDataSignTypedData
Browse files Browse the repository at this point in the history
  • Loading branch information
pkieltyka committed Dec 13, 2024
1 parent 2da2d7f commit a1289f9
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 6 deletions.
17 changes: 15 additions & 2 deletions intents/intent.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 15 additions & 4 deletions intents/intent.ridl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ enum IntentName: string
- sessionAuthProof
- feeOptions
- signMessage
- signTypedData
- sendTransaction
- getTransactionReceipt
- federateAccount
Expand Down Expand Up @@ -82,7 +83,7 @@ struct IntentDataValidateSession
struct IntentDataFinishValidateSession
- sessionId: string
+ go.field.name = SessionID
- wallet: string
- wallet: string
- salt: string
- challenge: string

Expand All @@ -101,19 +102,24 @@ struct IntentDataSessionAuthProof

struct IntentDataSignMessage
- network: string
- wallet: string
- wallet: string
- message: string

struct IntentDataSignTypedData
- network: string
- wallet: string
- typedData: any

struct IntentDataFeeOptions
- network: string
- wallet: string
- wallet: string
- identifier: string # is used to generate nonce space
- transactions: []any
+ go.field.type = []json.RawMessage

struct IntentDataSendTransaction
- network: string
- wallet: string
- wallet: string
- identifier: string # is used to generate nonce space
- transactions: []any
+ go.field.type = []json.RawMessage
Expand Down Expand Up @@ -252,6 +258,7 @@ enum IntentResponseCode: string
- validationFinished
- sessionAuthProof
- signedMessage
- signedTypedData
- feeOptions
- transactionReceipt
- transactionFailed
Expand Down Expand Up @@ -312,6 +319,10 @@ struct IntentResponseSignedMessage
- signature: string
- message: string

struct IntentResponseSignedTypedData
- signature: string
- encodedTypedData: string

struct FeeOption
- token: FeeToken
- to: string
Expand Down
86 changes: 86 additions & 0 deletions intents/intent_data_sign_712_ext.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package intents

import (
"bytes"
"encoding/json"
"fmt"
"math/big"

"github.com/0xsequence/ethkit/ethcoder"
"github.com/0xsequence/ethkit/go-ethereum/common"
"github.com/0xsequence/go-sequence"
)

func (p *IntentDataSignTypedData) UnmarshalJSON(data []byte) error {
type Raw struct {
Network string `json:"network"`
Wallet string `json:"wallet"`
TypedData json.RawMessage `json:"typedData"`
}

dec := json.NewDecoder(bytes.NewReader(data))
var raw Raw
if err := dec.Decode(&raw); err != nil {
return err
}

typedData, err := ethcoder.TypedDataFromJSON(string(raw.TypedData))

Check failure on line 27 in intents/intent_data_sign_712_ext.go

View workflow job for this annotation

GitHub Actions / Test

undefined: ethcoder.TypedDataFromJSON
if err != nil {
return err
}

p.Network = raw.Network
p.Wallet = raw.Wallet
p.TypedData = typedData
return nil
}

func (p *IntentDataSignTypedData) chainID() (*big.Int, error) {
n, ok := sequence.ParseHexOrDec(p.Network)
if !ok {
return nil, fmt.Errorf("invalid network id '%s'", p.Network)
}
return n, nil
}

func (p *IntentDataSignTypedData) message() ([]byte, error) {
typedData, ok := p.TypedData.(*ethcoder.TypedData)
if !ok {
return nil, fmt.Errorf("typedData field is not a valid typed data object")
}

_, encodedMessage, err := typedData.Encode()

Check failure on line 52 in intents/intent_data_sign_712_ext.go

View workflow job for this annotation

GitHub Actions / Test

typedData.Encode undefined (type *ethcoder.TypedData has no field or method Encode)
if err != nil {
return nil, err
}

return encodedMessage, nil
}

func (p *IntentDataSignTypedData) wallet() common.Address {
return common.HexToAddress(p.Wallet)
}

func (p *IntentDataSignTypedData) subdigest() ([]byte, error) {
chainID, err := p.chainID()
if err != nil {
return nil, err
}
msgData, err := p.message()
if err != nil {
return nil, err
}
return sequence.SubDigest(chainID, p.wallet(), sequence.MessageDigest(msgData))
}

// A SignMessagePacket (intent) *MUST* be mapped to a regular "SignMessage" Sequence action, this means that
// it must adhere to the following rules:
// - the subdigest must match `SubDigest(chainID, Wallet, Digest(Message))`
func (p *IntentDataSignTypedData) IsValidInterpretation(subdigest common.Hash) bool {
selfSubDigest, err := p.subdigest()
if err != nil {
return false
}

return bytes.Equal(selfSubDigest, subdigest[:])
}
103 changes: 103 additions & 0 deletions intents/intent_data_sign_712_ext_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package intents

import (
"encoding/json"
"math/big"
"testing"
"time"

"github.com/0xsequence/ethkit/ethcoder"
"github.com/0xsequence/ethkit/ethwallet"
"github.com/0xsequence/ethkit/go-ethereum/common"
"github.com/0xsequence/go-sequence"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestRecoverTypedDataIntent(t *testing.T) {
data := `{
"version": "1",
"name": "signTypedData",
"issued": 0,
"expires": 0,
"data": {
"wallet": "0xD67FC48b298B09Ed3D03403d930769C527186c4e",
"network": "1",
"typedData": {
"types": {
"EIP712Domain": [
{"name": "name", "type": "string"},
{"name": "version", "type": "string"},
{"name": "chainId", "type": "uint256"},
{"name": "verifyingContract", "type": "address"}
],
"Person": [
{"name": "name", "type": "string"},
{"name": "wallet", "type": "address"},
{"name": "count", "type": "uint8"}
]
},
"primaryType": "Person",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": 1,
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"name": "Bob",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
"count": 4
}
}
},
"signatures": []
}`

intent := &Intent{}
err := json.Unmarshal([]byte(data), intent)
require.Nil(t, err)

require.Equal(t, "1", intent.Version)
require.Equal(t, IntentName_signTypedData, intent.Name)

hash, err := intent.Hash()
require.Nil(t, err)
require.NotNil(t, common.Bytes2Hex(hash))

intent.IssuedAt = uint64(time.Now().Unix())
intent.ExpiresAt = uint64(time.Now().Unix()) + 60

wallet, err := ethwallet.NewWalletFromRandomEntropy()
require.Nil(t, err)

session := NewSessionP256K1(wallet)

err = session.Sign(intent)
require.Nil(t, err)

intentTyped, err := NewIntentTypedFromIntent[IntentDataSignTypedData](intent)
require.NoError(t, err)

typedData, ok := intentTyped.Data.TypedData.(*ethcoder.TypedData)
require.True(t, ok)
require.NotNil(t, typedData)

signers := intent.Signers()
require.Equal(t, 1, len(signers))
require.Equal(t, "0x"+common.Bytes2Hex(append([]byte{0x00}, wallet.Address().Bytes()...)), signers[0])

messageDigest, err := typedData.EncodeDigest()
require.NoError(t, err)
require.NotNil(t, messageDigest)

subdigest, err := sequence.SubDigest(
big.NewInt(1),
common.HexToAddress("0xD67FC48b298B09Ed3D03403d930769C527186c4e"),
common.BytesToHash(messageDigest),
)

assert.Nil(t, err)
assert.True(t, intentTyped.Data.IsValidInterpretation(common.BytesToHash(subdigest)))

}
2 changes: 2 additions & 0 deletions intents/intent_response_typed.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ func IntentResponseTypeToCode[T any](t *T) IntentResponseCode {
return IntentResponseCode_sessionAuthProof
case *IntentResponseSignedMessage:
return IntentResponseCode_signedMessage
case *IntentResponseSignedTypedData:
return IntentResponseCode_signedTypedData
case *IntentResponseFeeOptions:
return IntentResponseCode_feeOptions
case *IntentResponseTransactionReceipt:
Expand Down
2 changes: 2 additions & 0 deletions intents/intent_typed.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ func IntentDataTypeToName[T any](t *T) IntentName {
return IntentName_sessionAuthProof
case *IntentDataSignMessage:
return IntentName_signMessage
case *IntentDataSignTypedData:
return IntentName_signTypedData
case *IntentDataFeeOptions:
return IntentName_feeOptions
case *IntentDataSendTransaction:
Expand Down

0 comments on commit a1289f9

Please sign in to comment.