From 97c6a5b71b71be107650d1e36924f0bed2b6e17d Mon Sep 17 00:00:00 2001 From: Branimir Tomeljak Date: Fri, 17 Jul 2026 15:45:34 +0200 Subject: [PATCH] key across signature cache by the borrow digest fields --- api/handlers/signing.go | 19 ++++++++++- api/handlers/signing_test.go | 37 +++++++++++++++++++-- chains/evm/message/across.go | 4 ++- chains/evm/signature/session.go | 18 +++++++++++ chains/evm/signature/session_test.go | 48 ++++++++++++++++++++++++++++ 5 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 chains/evm/signature/session.go create mode 100644 chains/evm/signature/session_test.go diff --git a/api/handlers/signing.go b/api/handlers/signing.go index ba617f47..4d828b8a 100644 --- a/api/handlers/signing.go +++ b/api/handlers/signing.go @@ -7,10 +7,12 @@ import ( "fmt" "math/big" "net/http" + "strconv" "github.com/ethereum/go-ethereum/common" "github.com/gorilla/mux" evmMessage "github.com/sprintertech/sprinter-signing/chains/evm/message" + "github.com/sprintertech/sprinter-signing/chains/evm/signature" lighterMessage "github.com/sprintertech/sprinter-signing/chains/lighter/message" "github.com/sygmaprotocol/sygma-core/relayer/message" ) @@ -234,7 +236,7 @@ func (h *StatusHandler) HandleRequest(w http.ResponseWriter, r *http.Request) { ctx := r.Context() sigChn := make(chan []byte, 1) - h.cache.Subscribe(ctx, fmt.Sprintf("%d-%s", chainId, depositId), sigChn) + h.cache.Subscribe(ctx, subscribeKey(r, chainId.Uint64(), depositId), sigChn) for { select { case <-r.Context().Done(): @@ -249,6 +251,21 @@ func (h *StatusHandler) HandleRequest(w http.ResponseWriter, r *http.Request) { } } +// subscribeKey uses the composite key when the caller supplies the digest fields, else the legacy key. +func subscribeKey(r *http.Request, chainID uint64, depositID string) string { + q := r.URL.Query() + deadline, errD := strconv.ParseUint(q.Get("deadline"), 10, 64) + caller := q.Get("caller") + borrowAmount, okB := new(big.Int).SetString(q.Get("borrowAmount"), 10) + liquidityPool := q.Get("liquidityPool") + repaymentChainID, errR := strconv.ParseUint(q.Get("repaymentChainId"), 10, 64) + if errD != nil || caller == "" || !okB || liquidityPool == "" || errR != nil { + return fmt.Sprintf("%d-%s", chainID, depositID) + } + return signature.BorrowSessionID(chainID, depositID, deadline, common.HexToAddress(caller), + borrowAmount, common.HexToAddress(liquidityPool), repaymentChainID) +} + func (h *StatusHandler) setheaders(w http.ResponseWriter) { w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") diff --git a/api/handlers/signing_test.go b/api/handlers/signing_test.go index 820a46a9..762f8fc9 100644 --- a/api/handlers/signing_test.go +++ b/api/handlers/signing_test.go @@ -12,6 +12,7 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/common" "github.com/gorilla/mux" "github.com/sprintertech/sprinter-signing/api/handlers" mock_handlers "github.com/sprintertech/sprinter-signing/api/handlers/mock" @@ -472,7 +473,10 @@ func (s *StatusHandlerTestSuite) Test_HandleRequest_ChainNotSupported() { } func (s *StatusHandlerTestSuite) Test_HandleRequest_ValidSignature() { - req := httptest.NewRequest(http.MethodGet, "/v1/chains/1/signatures/id", nil) + caller := "0x1111111111111111111111111111111111111111" + pool := "0x3333333333333333333333333333333333333333" + query := "?deadline=1000&caller=" + caller + "&borrowAmount=500&liquidityPool=" + pool + "&repaymentChainId=10" + req := httptest.NewRequest(http.MethodGet, "/v1/chains/1/signatures/id"+query, nil) req = mux.SetURLVars(req, map[string]string{ "chainId": "1", "depositId": "id", @@ -480,9 +484,10 @@ func (s *StatusHandlerTestSuite) Test_HandleRequest_ValidSignature() { recorder := httptest.NewRecorder() + expectedKey := "1-id-1000-" + common.HexToAddress(caller).Hex() + "-500-" + common.HexToAddress(pool).Hex() + "-10" expectedSignature := []byte{0x01, 0x02, 0x03} s.mockSignatureCacher.EXPECT(). - Subscribe(gomock.Any(), "1-id", gomock.Any()). + Subscribe(gomock.Any(), expectedKey, gomock.Any()). Do(func(ctx context.Context, id string, sigChannel chan []byte) { go func() { sigChannel <- expectedSignature @@ -491,7 +496,7 @@ func (s *StatusHandlerTestSuite) Test_HandleRequest_ValidSignature() { go s.handler.HandleRequest(recorder, req) - time.Sleep(100 * time.Millisecond) // Give some time for the goroutine to execute + time.Sleep(50 * time.Millisecond) s.Equal(http.StatusOK, recorder.Code) s.Equal("text/event-stream", recorder.Header().Get("Content-Type")) @@ -500,3 +505,29 @@ func (s *StatusHandlerTestSuite) Test_HandleRequest_ValidSignature() { s.Equal("*", recorder.Header().Get("Access-Control-Allow-Origin")) s.Equal("data: "+hex.EncodeToString(expectedSignature)+"\n\n", recorder.Body.String()) } + +func (s *StatusHandlerTestSuite) Test_HandleRequest_LegacyKeyWithoutParams() { + req := httptest.NewRequest(http.MethodGet, "/v1/chains/1/signatures/id", nil) + req = mux.SetURLVars(req, map[string]string{ + "chainId": "1", + "depositId": "id", + }) + + recorder := httptest.NewRecorder() + + expectedSignature := []byte{0x01, 0x02, 0x03} + s.mockSignatureCacher.EXPECT(). + Subscribe(gomock.Any(), "1-id", gomock.Any()). + Do(func(ctx context.Context, id string, sigChannel chan []byte) { + go func() { + sigChannel <- expectedSignature + }() + }) + + go s.handler.HandleRequest(recorder, req) + + time.Sleep(100 * time.Millisecond) // Give some time for the goroutine to execute + + s.Equal(http.StatusOK, recorder.Code) + s.Equal("data: "+hex.EncodeToString(expectedSignature)+"\n\n", recorder.Body.String()) +} diff --git a/chains/evm/message/across.go b/chains/evm/message/across.go index 806818eb..d98a6f20 100644 --- a/chains/evm/message/across.go +++ b/chains/evm/message/across.go @@ -197,7 +197,9 @@ func (h *AcrossMessageHandler) HandleMessage(m *message.Message) (*proposal.Prop return nil, err } - sessionID := fmt.Sprintf("%d-%s", sourceChainID, data.DepositId) + sessionID := signature.BorrowSessionID( + sourceChainID, data.DepositId.String(), data.Deadline, data.Caller, + data.BorrowAmount, data.LiquidityPool, data.RepaymentChainID) signing, err := signing.NewSigning( new(big.Int).SetBytes(unlockHash), sessionID, diff --git a/chains/evm/signature/session.go b/chains/evm/signature/session.go new file mode 100644 index 00000000..3f9f8268 --- /dev/null +++ b/chains/evm/signature/session.go @@ -0,0 +1,18 @@ +package signature + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +// BorrowSessionID keys the signing session and cache by the digest fields not already fixed by the deposit. +func BorrowSessionID( + chainID uint64, depositID string, deadline uint64, caller common.Address, + borrowAmount *big.Int, liquidityPool common.Address, repaymentChainID uint64, +) string { + return fmt.Sprintf("%d-%s-%d-%s-%s-%s-%d", + chainID, depositID, deadline, caller.Hex(), + borrowAmount.String(), liquidityPool.Hex(), repaymentChainID) +} diff --git a/chains/evm/signature/session_test.go b/chains/evm/signature/session_test.go new file mode 100644 index 00000000..4032b88c --- /dev/null +++ b/chains/evm/signature/session_test.go @@ -0,0 +1,48 @@ +package signature_test + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/sprintertech/sprinter-signing/chains/evm/signature" +) + +func TestBorrowSessionID(t *testing.T) { + caller := common.HexToAddress("0x1111111111111111111111111111111111111111") + other := common.HexToAddress("0x2222222222222222222222222222222222222222") + pool := common.HexToAddress("0x3333333333333333333333333333333333333333") + otherPool := common.HexToAddress("0x4444444444444444444444444444444444444444") + id := func(chainID uint64, deposit string, deadline uint64, c common.Address, + amount *big.Int, lp common.Address, repay uint64) string { + return signature.BorrowSessionID(chainID, deposit, deadline, c, amount, lp, repay) + } + base := id(1, "42", 1000, caller, big.NewInt(500), pool, 10) + + tests := []struct { + name string + got string + equal bool + }{ + {"identical inputs match", id(1, "42", 1000, caller, big.NewInt(500), pool, 10), true}, + {"different chain differs", id(2, "42", 1000, caller, big.NewInt(500), pool, 10), false}, + {"different deposit differs", id(1, "43", 1000, caller, big.NewInt(500), pool, 10), false}, + {"different deadline differs", id(1, "42", 1001, caller, big.NewInt(500), pool, 10), false}, + {"different caller differs", id(1, "42", 1000, other, big.NewInt(500), pool, 10), false}, + {"different borrow amount differs", id(1, "42", 1000, caller, big.NewInt(501), pool, 10), false}, + {"different liquidity pool differs", id(1, "42", 1000, caller, big.NewInt(500), otherPool, 10), false}, + {"different repayment chain differs", id(1, "42", 1000, caller, big.NewInt(500), pool, 11), false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if (tt.got == base) != tt.equal { + t.Fatalf("equal=%v, base=%q got=%q", tt.equal, base, tt.got) + } + }) + } + + want := "1-42-1000-" + caller.Hex() + "-500-" + pool.Hex() + "-10" + if base != want { + t.Fatalf("format: want %q got %q", want, base) + } +}