diff --git a/go.mod b/go.mod index 41f241729f..c01113d511 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.21.11 replace github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220224222438-c78f6963a1c0+incompatible require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v1.1.0 github.com/FantasyJony/openzeppelin-merkle-tree-go v1.1.3 github.com/Microsoft/go-winio v0.6.2 @@ -60,7 +61,6 @@ require ( require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/DataDog/zstd v1.5.6 // indirect diff --git a/go.sum b/go.sum index 41524fd3a0..026692c79c 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,6 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= diff --git a/integration/tengateway/tengateway_test.go b/integration/tengateway/tengateway_test.go index a63b8fde86..92c632f264 100644 --- a/integration/tengateway/tengateway_test.go +++ b/integration/tengateway/tengateway_test.go @@ -12,6 +12,11 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ten-protocol/go-ten/go/common/gethapi" + + "github.com/ten-protocol/go-ten/go/responses" + "github.com/ten-protocol/go-ten/lib/gethfork/rpc" "github.com/ten-protocol/go-ten/tools/walletextension" @@ -51,7 +56,7 @@ func init() { //nolint:gochecknoinits LogDir: testLogs, TestType: "tengateway", TestSubtype: "test", - LogLevel: log.LvlInfo, + LogLevel: log.LvlTrace, }) } @@ -115,6 +120,7 @@ func TestTenGateway(t *testing.T) { "testInvokeNonSensitiveMethod": testInvokeNonSensitiveMethod, "testGetStorageAtForReturningUserID": testGetStorageAtForReturningUserID, "testRateLimiter": testRateLimiter, + "testSessionKeys": testSessionKeys, } { t.Run(name, func(t *testing.T) { test(t, startPort, httpURL, wsURL, w) @@ -167,6 +173,141 @@ func testRateLimiter(t *testing.T, _ int, httpURL, wsURL string, w wallet.Wallet require.Equal(t, "rate limit exceeded", err.Error()) } +func testSessionKeys(t *testing.T, _ int, httpURL, wsURL string, w wallet.Wallet) { + user0, err := NewGatewayUser([]wallet.Wallet{w, datagenerator.RandomWallet(integration.TenChainID)}, httpURL, wsURL) + require.NoError(t, err) + testlog.Logger().Info("Created user with encryption token", "t", user0.tgClient.UserID()) + // register the user so we can call the endpoints that require authentication + err = user0.RegisterAccounts() + require.NoError(t, err) + + var amountToTransfer int64 = 1_000_000_000_000_000_000 + // Transfer some funds to user1 to be able to make transactions + _, err = transferETHToAddress(user0.HTTPClient, user0.Wallets[0], user0.Wallets[0].Address(), amountToTransfer) + require.NoError(t, err) + + // call BalanceAt - fist call should be successful + _, err = user0.HTTPClient.BalanceAt(context.Background(), user0.Wallets[0].Address(), nil) + require.NoError(t, err) + + contractAddr := deployContract(t, w, user0) + + // create session key + skAddr, err := user0.HTTPClient.StorageAt(context.Background(), gethcommon.HexToAddress(common.CreateSessionKeyCQMethod), gethcommon.Hash{}, nil) + require.NoError(t, err) + skAddress := gethcommon.BytesToAddress(skAddr) + + // move some funds to the SK + var skAmount int64 = 100_000_000_000_000_000 + _, err = transferETHToAddress(user0.HTTPClient, user0.Wallets[0], skAddress, skAmount) + require.NoError(t, err) + + // activate SK + _, err = user0.HTTPClient.StorageAt(context.Background(), gethcommon.HexToAddress(common.ActivateSessionKeyCQMethod), gethcommon.Hash{}, nil) + require.NoError(t, err) + + skNonce := uint64(0) + + // interact with the contract - unsigned tx calling "sendRawTransaction" + contractInteractionData, err := eventsContractABI.Pack("setMessage", "user0PrivateEvent") + require.NoError(t, err) + rec, err := interactWithSmartContractUnsigned(user0.HTTPClient, true, skNonce, contractAddr, contractInteractionData, nil) + require.NoError(t, err) + require.Equal(t, uint64(0x1), rec.Status) + + // move money back - unsigned tx calling "sendTransaction" + skNonce++ + rec1, err := interactWithSmartContractUnsigned(user0.HTTPClient, false, skNonce, user0.Wallets[0].Address(), nil, big.NewInt(1_000)) + require.NoError(t, err) + require.Equal(t, uint64(0x1), rec1.Status) + + // deactivate + _, err = user0.HTTPClient.StorageAt(context.Background(), gethcommon.HexToAddress(common.DeactivateSessionKeyCQMethod), gethcommon.Hash{}, nil) + require.NoError(t, err) + + // interact with the contract - unsigned - should fail + skNonce++ + rec2, err := interactWithSmartContractUnsigned(user0.HTTPClient, false, skNonce, contractAddr, contractInteractionData, nil) + require.Error(t, err) + require.Nil(t, rec2) +} + +func deployContract(t *testing.T, w wallet.Wallet, user0 *GatewayUser) gethcommon.Address { + // deploy events contract + deployTx := &types.LegacyTx{ + Nonce: w.GetNonceAndIncrement(), + Gas: uint64(1_000_000), + GasPrice: gethcommon.Big1, + Data: gethcommon.FromHex(eventsContractBytecode), + } + + err := getFeeAndGas(user0.HTTPClient, w, deployTx) + require.NoError(t, err) + + signedTx, err := w.SignTransaction(deployTx) + require.NoError(t, err) + + err = user0.HTTPClient.SendTransaction(context.Background(), signedTx) + require.NoError(t, err) + + contractReceipt, err := integrationCommon.AwaitReceiptEth(context.Background(), user0.HTTPClient, signedTx.Hash(), time.Minute) + require.NoError(t, err) + return contractReceipt.ContractAddress +} + +func interactWithSmartContractUnsigned(client *ethclient.Client, sendRaw bool, nonce uint64, contractAddress gethcommon.Address, contractInteractionData []byte, value *big.Int) (*types.Receipt, error) { + var result responses.GasPriceType + err := client.Client().CallContext(context.Background(), &result, tenrpc.GasPrice) + if err != nil { + return nil, err + } + + var txHash gethcommon.Hash + + if sendRaw { + interactionTx := types.LegacyTx{ + Nonce: nonce, + To: &contractAddress, + Gas: uint64(10_000_000), + GasPrice: result.ToInt(), + Data: contractInteractionData, + Value: value, + } + unSignedTx := types.NewTx(&interactionTx) + blob, err := unSignedTx.MarshalBinary() + if err != nil { + return nil, err + } + err = client.Client().CallContext(context.Background(), &txHash, "eth_sendRawTransaction", hexutil.Encode(blob)) + if err != nil { + return nil, err + } + } else { + n := hexutil.Uint64(nonce) + g := hexutil.Uint64(10_000_000) + d := hexutil.Bytes(contractInteractionData) + interactionTx := gethapi.TransactionArgs{ + Nonce: &n, + To: &contractAddress, + Gas: &g, + GasPrice: &result, + Data: &d, + Value: (*hexutil.Big)(value), + } + err = client.Client().CallContext(context.Background(), &txHash, "eth_sendTransaction", interactionTx) + if err != nil { + return nil, err + } + } + + txReceipt, err := integrationCommon.AwaitReceiptEth(context.Background(), client, txHash, 10*time.Second) + if err != nil { + return nil, err + } + + return txReceipt, nil +} + func testNewHeadsSubscription(t *testing.T, _ int, httpURL, wsURL string, w wallet.Wallet) { user0, err := NewGatewayUser([]wallet.Wallet{w, datagenerator.RandomWallet(integration.TenChainID)}, httpURL, wsURL) require.NoError(t, err) diff --git a/tools/walletextension/rpcapi/transaction_api.go b/tools/walletextension/rpcapi/transaction_api.go index f7f89aad73..f4169d0709 100644 --- a/tools/walletextension/rpcapi/transaction_api.go +++ b/tools/walletextension/rpcapi/transaction_api.go @@ -2,6 +2,7 @@ package rpcapi import ( "context" + "fmt" "github.com/ten-protocol/go-ten/tools/walletextension/cache" @@ -103,13 +104,26 @@ func (s *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common. } func (s *TransactionAPI) SendTransaction(ctx context.Context, args gethapi.TransactionArgs) (common.Hash, error) { - //txRec, err := ExecAuthRPC[common.Hash](ctx, s.we, &AuthExecCfg{account: args.From, timeout: sendTransactionDuration}, "eth_sendTransaction", args) - //if err != nil { - // return common.Hash{}, err - //} - //return *txRec, err - // not implemented for now. We might use this for session keys. - return common.Hash{}, rpcNotImplemented + user, err := extractUserForRequest(ctx, s.we) + if err != nil { + return common.Hash{}, err + } + if !user.ActiveSK { + return common.Hash{}, fmt.Errorf("please activate session key") + } + + // when there is an active Session Key, sign all incoming transactions with that SK + signedTx, err := s.we.SKManager.SignTx(ctx, user, args.ToTransaction()) + if err != nil { + return common.Hash{}, err + } + + blob, err := signedTx.MarshalBinary() + if err != nil { + return common.Hash{}, err + } + + return s.sendRawTx(ctx, blob) } type SignTransactionResult struct { @@ -127,16 +141,28 @@ func (s *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.B return common.Hash{}, err } - signedTx := input + signedTxBlob := input // when there is an active Session Key, sign all incoming transactions with that SK if user.ActiveSK && user.SessionKey != nil { - signedTx, err = s.we.SKManager.SignTx(ctx, user, input) + tx := new(types.Transaction) + if err = tx.UnmarshalBinary(input); err != nil { + return common.Hash{}, err + } + signedTx, err := s.we.SKManager.SignTx(ctx, user, tx) + if err != nil { + return common.Hash{}, err + } + signedTxBlob, err = signedTx.MarshalBinary() if err != nil { return common.Hash{}, err } } - txRec, err := ExecAuthRPC[common.Hash](ctx, s.we, &AuthExecCfg{tryAll: true, timeout: sendTransactionDuration}, "eth_sendRawTransaction", signedTx) + return s.sendRawTx(ctx, signedTxBlob) +} + +func (s *TransactionAPI) sendRawTx(ctx context.Context, input hexutil.Bytes) (common.Hash, error) { + txRec, err := ExecAuthRPC[common.Hash](ctx, s.we, &AuthExecCfg{tryAll: true, timeout: sendTransactionDuration}, "eth_sendRawTransaction", input) if err != nil { return common.Hash{}, err } diff --git a/tools/walletextension/services/sk_manager.go b/tools/walletextension/services/sk_manager.go index d87a62c8e3..ca2ad88566 100644 --- a/tools/walletextension/services/sk_manager.go +++ b/tools/walletextension/services/sk_manager.go @@ -3,8 +3,8 @@ package services import ( "context" "fmt" + "math/big" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -23,7 +23,7 @@ import ( // From the POV of the Ten network - a session key is a normal account key type SKManager interface { CreateSessionKey(user *common.GWUser) (*common.GWSessionKey, error) - SignTx(ctx context.Context, user *common.GWUser, input hexutil.Bytes) (hexutil.Bytes, error) + SignTx(ctx context.Context, user *common.GWUser, input *types.Transaction) (*types.Transaction, error) } type skManager struct { @@ -92,17 +92,16 @@ func (m *skManager) createSK(user *common.GWUser) (*common.GWSessionKey, error) }, nil } -func (m *skManager) SignTx(ctx context.Context, user *common.GWUser, input hexutil.Bytes) (hexutil.Bytes, error) { - tx := new(types.Transaction) - if err := tx.UnmarshalBinary(input); err != nil { - return hexutil.Bytes{}, err - } - - signer := types.NewLondonSigner(tx.ChainId()) +func (m *skManager) SignTx(ctx context.Context, user *common.GWUser, tx *types.Transaction) (*types.Transaction, error) { + prvKey := user.SessionKey.PrivateKey.ExportECDSA() + signer := types.NewCancunSigner(big.NewInt(int64(m.config.TenChainID))) - tx, err := types.SignTx(tx, signer, user.SessionKey.PrivateKey.ExportECDSA()) + stx, err := types.SignTx(tx, signer, prvKey) if err != nil { - return hexutil.Bytes{}, err + return nil, err } - return tx.MarshalBinary() + + m.logger.Debug("Signed transaction with session key", "stxHash", stx.Hash().Hex()) + + return stx, nil } diff --git a/tools/walletextension/storage/database/common/db_types.go b/tools/walletextension/storage/database/common/db_types.go index ea1b836047..41f133c787 100644 --- a/tools/walletextension/storage/database/common/db_types.go +++ b/tools/walletextension/storage/database/common/db_types.go @@ -1,9 +1,10 @@ package common import ( - "crypto/x509" "fmt" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto/ecies" "github.com/ten-protocol/go-ten/go/common/viewingkey" @@ -50,7 +51,7 @@ func (userDB *GWUserDB) ToGWUser() (*wecommon.GWUser, error) { } if userDB.SessionKey != nil { - ecdsaPrivateKey, err := x509.ParseECPrivateKey(userDB.SessionKey.PrivateKey) + ecdsaPrivateKey, err := crypto.ToECDSA(userDB.SessionKey.PrivateKey) if err != nil { return nil, fmt.Errorf("failed to parse ECDSA private key: %w", err) }