Skip to content

Commit 8f4d459

Browse files
committed
Merge branch 'master' of github.com:0xsequence/go-sequence into relayer-front-update
2 parents 6d487de + 711a225 commit 8f4d459

File tree

3 files changed

+238
-50
lines changed

3 files changed

+238
-50
lines changed

intent_config.go

Lines changed: 37 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -186,17 +186,17 @@ func CreateAnyAddressSubdigestTree(calls []*v3.CallsPayload) ([]v3.WalletConfigT
186186
return leaves, nil
187187
}
188188

189-
// `CreateIntentTree` creates a tree from a list of intent operations and a main signer address.
190-
func CreateIntentTree(mainSigner common.Address, calls []*v3.CallsPayload, sapientSignerLeafNode v3.WalletConfigTree) (*v3.WalletConfigTree, error) {
189+
func createIntentTree(mainSigner common.Address, calls []*v3.CallsPayload, additionalLeaves ...v3.WalletConfigTree) (*v3.WalletConfigTree, error) {
191190
// Create the subdigest leaves from the batched transactions.
192191
leaves, err := CreateAnyAddressSubdigestTree(calls)
193192
if err != nil {
194193
return nil, err
195194
}
196195

197-
// If the sapient signer leaf is not nil, add it to the leaves.
198-
if sapientSignerLeafNode != nil {
199-
leaves = append(leaves, sapientSignerLeafNode)
196+
for _, leaf := range additionalLeaves {
197+
if leaf != nil {
198+
leaves = append(leaves, leaf)
199+
}
200200
}
201201

202202
// Create the main signer leaf (with weight 1).
@@ -220,39 +220,36 @@ func CreateIntentTree(mainSigner common.Address, calls []*v3.CallsPayload, sapie
220220
return &fullTree, nil
221221
}
222222

223-
// `CreateIntentConfiguration` creates a wallet configuration where the intent's transaction batches are grouped into the initial subdigest.
224-
func CreateIntentConfiguration(mainSigner common.Address, calls []*v3.CallsPayload, sapientSignerLeafNode v3.WalletConfigTree) (*v3.WalletConfig, error) {
225-
// Create the subdigest leaves from the batched transactions.
226-
tree, err := CreateIntentTree(mainSigner, calls, sapientSignerLeafNode)
223+
// `CreateIntentTree` creates a tree from a list of intent operations and a main signer address.
224+
func CreateIntentTree(mainSigner common.Address, calls []*v3.CallsPayload, sapientSignerLeafNode v3.WalletConfigTree) (*v3.WalletConfigTree, error) {
225+
return createIntentTree(mainSigner, calls, sapientSignerLeafNode)
226+
}
227+
228+
func createIntentConfiguration(mainSigner common.Address, calls []*v3.CallsPayload, additionalLeaves ...v3.WalletConfigTree) (*v3.WalletConfig, error) {
229+
tree, err := createIntentTree(mainSigner, calls, additionalLeaves...)
227230
if err != nil {
228231
return nil, err
229232
}
230233

231-
// Construct the new wallet config using:
232-
config := &v3.WalletConfig{
234+
return &v3.WalletConfig{
233235
Threshold_: 1,
234236
Checkpoint_: 0,
235237
Tree: *tree,
236-
}
238+
}, nil
239+
}
237240

238-
return config, nil
241+
// `CreateIntentConfiguration` creates a wallet configuration where the intent's transaction batches are grouped into the initial subdigest.
242+
func CreateIntentConfiguration(mainSigner common.Address, calls []*v3.CallsPayload, sapientSignerLeafNode v3.WalletConfigTree) (*v3.WalletConfig, error) {
243+
return createIntentConfiguration(mainSigner, calls, sapientSignerLeafNode)
239244
}
240245

241-
// `GetIntentConfigurationSignature` creates a signature for the intent configuration that can be used to bypass chain ID validation.
242-
// The signature is based on the transaction bundle digests only.
243-
func GetIntentConfigurationSignature(
244-
mainSigner common.Address,
245-
calls []*v3.CallsPayload,
246-
) ([]byte, error) {
247-
// Default case without any sapient signer
248-
config, err := CreateIntentConfiguration(mainSigner, calls, nil)
249-
if err != nil {
250-
return nil, err
246+
// `BuildIntentConfigurationSignature` creates a signature for an already-built intent configuration
247+
// that can be used to bypass chain ID validation.
248+
func BuildIntentConfigurationSignature(config *v3.WalletConfig) ([]byte, error) {
249+
if config == nil {
250+
return nil, fmt.Errorf("intent configuration is nil")
251251
}
252252

253-
// spew.Dump(config)
254-
// spew.Dump(config.Tree)
255-
256253
signingFunc := func(ctx context.Context, signer core.Signer, _ []core.SignerSignature) (core.SignerSignatureType, []byte, error) {
257254
// For mainSigner or other signers, we don't provide a signature here.
258255
// This will result in an AddressLeaf or NodeLeaf in the signature tree.
@@ -266,30 +263,6 @@ func GetIntentConfigurationSignature(
266263
return nil, fmt.Errorf("failed to build regular signature: %w", err)
267264
}
268265

269-
// spew.Dump(sig)
270-
271-
if regularSig, ok := sig.(*v3.RegularSignature); ok {
272-
if regularSig.Signature != nil {
273-
signatureTree := regularSig.Signature.Tree
274-
_ = signatureTree
275-
// fmt.Println("Accessing sig.Signature.Tree:")
276-
// spew.Dump(signatureTree)
277-
} else {
278-
// fmt.Println("sig.Signature is nil")
279-
}
280-
} else if noChainIdSig, ok := sig.(*v3.NoChainIDSignature); ok {
281-
if noChainIdSig.Signature != nil {
282-
signatureTree := noChainIdSig.Signature.Tree
283-
_ = signatureTree
284-
// fmt.Println("Accessing sig.Signature.Tree (NoChainID):")
285-
// spew.Dump(signatureTree)
286-
} else {
287-
// fmt.Println("sig.Signature is nil for NoChainIDSignature")
288-
}
289-
} else {
290-
// fmt.Printf("sig is not of type *v3.RegularSignature or *v3.NoChainIDSignature, it is %T\n", sig)
291-
}
292-
293266
// Get the signature data
294267
data, err := sig.Data()
295268
if err != nil {
@@ -302,6 +275,20 @@ func GetIntentConfigurationSignature(
302275
return data, nil
303276
}
304277

278+
// `GetIntentConfigurationSignature` creates a signature for the intent configuration that can be used to bypass chain ID validation.
279+
// The signature is based on the transaction bundle digests only.
280+
func GetIntentConfigurationSignature(
281+
mainSigner common.Address,
282+
calls []*v3.CallsPayload,
283+
) ([]byte, error) {
284+
config, err := CreateIntentConfiguration(mainSigner, calls, nil)
285+
if err != nil {
286+
return nil, err
287+
}
288+
289+
return BuildIntentConfigurationSignature(config)
290+
}
291+
305292
// // replaceSapientSignerWithNodeInConfigTree recursively traverses the WalletConfigTree.
306293
// func replaceSapientSignerWithNodeInConfigTree(tree v3.WalletConfigTree) v3.WalletConfigTree {
307294
// if tree == nil {

intent_config_test.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,24 @@ import (
2323
"github.com/stretchr/testify/require"
2424
)
2525

26+
func findSapientSignerLeaf(tree v3.WalletConfigTree, address common.Address) *v3.WalletConfigTreeSapientSignerLeaf {
27+
switch node := tree.(type) {
28+
case *v3.WalletConfigTreeSapientSignerLeaf:
29+
if node.Address == address {
30+
return node
31+
}
32+
case *v3.WalletConfigTreeNode:
33+
if leaf := findSapientSignerLeaf(node.Left, address); leaf != nil {
34+
return leaf
35+
}
36+
return findSapientSignerLeaf(node.Right, address)
37+
case *v3.WalletConfigTreeNestedLeaf:
38+
return findSapientSignerLeaf(node.Tree, address)
39+
}
40+
41+
return nil
42+
}
43+
2644
func TestCreateIntentCallsPayload_Valid(t *testing.T) {
2745
// Create a calls payload
2846
calls := []v3.Call{
@@ -304,6 +322,89 @@ func TestCreateIntentConfiguration_Valid(t *testing.T) {
304322
require.NotNil(t, config)
305323
}
306324

325+
func TestCreateIntentConfigurationWithTimedRefundSapient(t *testing.T) {
326+
payload := v3.NewCallsPayload(common.Address{}, testChain.ChainID(), []v3.Call{
327+
{
328+
To: common.HexToAddress("0x1111111111111111111111111111111111111111"),
329+
Value: nil,
330+
Data: []byte{0x12, 0x34},
331+
GasLimit: big.NewInt(0),
332+
DelegateCall: false,
333+
OnlyFallback: false,
334+
BehaviorOnError: v3.BehaviorOnErrorRevert,
335+
},
336+
}, big.NewInt(0), big.NewInt(0))
337+
338+
mainSigner := common.HexToAddress("0x2222222222222222222222222222222222222222")
339+
timedRefundSigner := common.HexToAddress("0x3333333333333333333333333333333333333333")
340+
destination := common.HexToAddress("0x4444444444444444444444444444444444444444")
341+
342+
config, err := sequence.CreateIntentConfigurationWithTimedRefundSapient(
343+
mainSigner,
344+
[]*v3.CallsPayload{&payload},
345+
sequence.TimedRefundIntentConfigurationSigner{
346+
Address: timedRefundSigner,
347+
Destination: destination,
348+
UnlockTimestamp: 1_750_000_000,
349+
Weight: 1,
350+
},
351+
)
352+
require.NoError(t, err)
353+
require.NotNil(t, config)
354+
355+
// Solidity reference (trails-contracts/src/autoRecovery/TimedRefundSapient.sol):
356+
// keccak256(abi.encode("timed-refund", destination, uint256(1750000000)))
357+
expectedSapientImageHash := common.HexToHash("0x577e11f2280512fff4541fc08cc7eb98357bdcff482db5634db7327e3c97ba58")
358+
sapientLeaf := findSapientSignerLeaf(config.Tree, timedRefundSigner)
359+
require.NotNil(t, sapientLeaf)
360+
require.Equal(t, expectedSapientImageHash, sapientLeaf.ImageHash_.Hash)
361+
362+
plainConfig, err := sequence.CreateIntentConfiguration(mainSigner, []*v3.CallsPayload{&payload}, nil)
363+
require.NoError(t, err)
364+
require.NotEqual(t, plainConfig.ImageHash().Hash, config.ImageHash().Hash)
365+
366+
signature, err := sequence.BuildIntentConfigurationSignature(config)
367+
require.NoError(t, err)
368+
require.NotEmpty(t, signature)
369+
370+
sig, err := v3.Core.DecodeSignature(signature)
371+
require.NoError(t, err)
372+
373+
recoveredConfig, _, err := sig.Recover(context.Background(), payload, nil)
374+
require.NoError(t, err)
375+
require.Equal(t, config.ImageHash().Hash, recoveredConfig.ImageHash().Hash)
376+
377+
plainSignature, err := sequence.GetIntentConfigurationSignature(mainSigner, []*v3.CallsPayload{&payload})
378+
require.NoError(t, err)
379+
require.NotEqual(t, plainSignature, signature)
380+
}
381+
382+
func TestCreateIntentConfigurationWithTimedRefundSapient_ZeroWeight(t *testing.T) {
383+
payload := v3.NewCallsPayload(common.Address{}, testChain.ChainID(), []v3.Call{
384+
{
385+
To: common.HexToAddress("0x1111111111111111111111111111111111111111"),
386+
Value: nil,
387+
Data: []byte{0x12, 0x34},
388+
GasLimit: big.NewInt(0),
389+
DelegateCall: false,
390+
OnlyFallback: false,
391+
BehaviorOnError: v3.BehaviorOnErrorRevert,
392+
},
393+
}, big.NewInt(0), big.NewInt(0))
394+
395+
_, err := sequence.CreateIntentConfigurationWithTimedRefundSapient(
396+
common.HexToAddress("0x2222222222222222222222222222222222222222"),
397+
[]*v3.CallsPayload{&payload},
398+
sequence.TimedRefundIntentConfigurationSigner{
399+
Address: common.HexToAddress("0x3333333333333333333333333333333333333333"),
400+
Destination: common.HexToAddress("0x4444444444444444444444444444444444444444"),
401+
UnlockTimestamp: 1_750_000_000,
402+
Weight: 0,
403+
},
404+
)
405+
require.EqualError(t, err, "timed refund sapient signer weight is zero")
406+
}
407+
307408
func TestGetIntentConfigurationSignature(t *testing.T) {
308409
// Create test wallets
309410
eoa1, err := ethwallet.NewWalletFromRandomEntropy()

intent_config_timed_refund.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package sequence
2+
3+
import (
4+
"fmt"
5+
"math/big"
6+
7+
"github.com/0xsequence/ethkit/go-ethereum/accounts/abi"
8+
"github.com/0xsequence/ethkit/go-ethereum/common"
9+
"github.com/0xsequence/ethkit/go-ethereum/crypto"
10+
"github.com/0xsequence/go-sequence/core"
11+
v3 "github.com/0xsequence/go-sequence/core/v3"
12+
)
13+
14+
var timedRefundSapientImageHashArguments = mustTimedRefundSapientImageHashArguments()
15+
16+
// TimedRefundIntentConfigurationSigner represents the dedicated timed-refund sapient signer
17+
// attached to an intent configuration. Weight must be greater than zero.
18+
type TimedRefundIntentConfigurationSigner struct {
19+
Address common.Address
20+
Destination common.Address
21+
UnlockTimestamp uint64
22+
Weight uint8
23+
}
24+
25+
// CreateIntentConfigurationWithTimedRefundSapient creates an intent configuration that includes
26+
// a timed-refund sapient signer leaf in addition to the default any-address subdigests.
27+
func CreateIntentConfigurationWithTimedRefundSapient(
28+
mainSigner common.Address,
29+
calls []*v3.CallsPayload,
30+
timedRefundSigner TimedRefundIntentConfigurationSigner,
31+
) (*v3.WalletConfig, error) {
32+
timedRefundLeaf, err := createTimedRefundSapientSignerLeaf(timedRefundSigner)
33+
if err != nil {
34+
return nil, err
35+
}
36+
37+
return createIntentConfiguration(mainSigner, calls, timedRefundLeaf)
38+
}
39+
40+
func createTimedRefundSapientSignerLeaf(signer TimedRefundIntentConfigurationSigner) (*v3.WalletConfigTreeSapientSignerLeaf, error) {
41+
if signer.Address == (common.Address{}) {
42+
return nil, fmt.Errorf("timed refund sapient signer address is zero")
43+
}
44+
if signer.Destination == (common.Address{}) {
45+
return nil, fmt.Errorf("timed refund destination is zero")
46+
}
47+
if signer.UnlockTimestamp == 0 {
48+
return nil, fmt.Errorf("timed refund unlock timestamp is zero")
49+
}
50+
if signer.Weight == 0 {
51+
return nil, fmt.Errorf("timed refund sapient signer weight is zero")
52+
}
53+
54+
imageHash, err := timedRefundSapientImageHash(signer.Destination, signer.UnlockTimestamp)
55+
if err != nil {
56+
return nil, err
57+
}
58+
59+
return &v3.WalletConfigTreeSapientSignerLeaf{
60+
Weight: signer.Weight,
61+
Address: signer.Address,
62+
ImageHash_: imageHash,
63+
}, nil
64+
}
65+
66+
func mustTimedRefundSapientImageHashArguments() abi.Arguments {
67+
stringType, err := abi.NewType("string", "", nil)
68+
if err != nil {
69+
panic(fmt.Errorf("create timed refund string ABI type: %w", err))
70+
}
71+
72+
addressType, err := abi.NewType("address", "", nil)
73+
if err != nil {
74+
panic(fmt.Errorf("create timed refund address ABI type: %w", err))
75+
}
76+
77+
uint256Type, err := abi.NewType("uint256", "", nil)
78+
if err != nil {
79+
panic(fmt.Errorf("create timed refund uint256 ABI type: %w", err))
80+
}
81+
82+
return abi.Arguments{
83+
{Type: stringType},
84+
{Type: addressType},
85+
{Type: uint256Type},
86+
}
87+
}
88+
89+
func timedRefundSapientImageHash(destination common.Address, unlockTimestamp uint64) (core.ImageHash, error) {
90+
encoded, err := timedRefundSapientImageHashArguments.Pack(
91+
"timed-refund",
92+
destination,
93+
new(big.Int).SetUint64(unlockTimestamp),
94+
)
95+
if err != nil {
96+
return core.ImageHash{}, fmt.Errorf("failed to ABI pack timed refund sapient image hash: %w", err)
97+
}
98+
99+
return core.ImageHash{Hash: crypto.Keccak256Hash(encoded)}, nil
100+
}

0 commit comments

Comments
 (0)