Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5dbb5c5
extend api for proxy
noel2004 Aug 24, 2025
1f2b857
add proxy_login route
noel2004 Aug 24, 2025
9796d16
WIP: update login logic and coordinator client
noel2004 Aug 24, 2025
412ad56
extend loginlogic
noel2004 Aug 24, 2025
3adb2e0
WIP: controller
noel2004 Aug 24, 2025
5c6c225
WIP: config and client controller
noel2004 Aug 24, 2025
76ecdf0
add proxy config sample
noel2004 Aug 24, 2025
0d238d7
WIP: the structure of client manager
noel2004 Aug 24, 2025
7b3a65b
framework for auto login
noel2004 Aug 24, 2025
4f878d9
AI step
noel2004 Aug 24, 2025
624a7a2
WIP: AI step
noel2004 Aug 25, 2025
321dd43
unit test for client
noel2004 Aug 25, 2025
64ef0f4
WIP
noel2004 Aug 25, 2025
5a07a16
WIP
noel2004 Aug 27, 2025
5614ec3
WIP
noel2004 Sep 1, 2025
322766f
WIP
noel2004 Sep 2, 2025
4725d8a
Merge remote-tracking branch 'origin/develop' into coordinator_proxy
noel2004 Sep 2, 2025
c72ee5d
Merge remote-tracking branch 'origin/develop' into coordinator_proxy
noel2004 Sep 3, 2025
e6be62f
WIP
noel2004 Sep 5, 2025
9df6429
wip
noel2004 Sep 6, 2025
78dbe6c
controller WIP
noel2004 Sep 7, 2025
a04b64d
routes
noel2004 Sep 8, 2025
2721503
refining
noel2004 Sep 9, 2025
50f3e1a
fix issues from test
noel2004 Sep 9, 2025
256c90a
Merge remote-tracking branch 'origin/develop' into coordinator_proxy
noel2004 Sep 9, 2025
92ca7a6
improve get_task proxy
noel2004 Sep 10, 2025
c7b83a0
fix issue in test
noel2004 Sep 10, 2025
057e220
fix issues
noel2004 Sep 10, 2025
b7f23c6
basic tests
noel2004 Sep 10, 2025
1d9fa41
Merge remote-tracking branch 'origin/develop' into coordinator_proxy
noel2004 Sep 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions common/types/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"net/http"

"github.com/gin-gonic/gin"
"github.com/mitchellh/mapstructure"
)

// Response the response schema
Expand All @@ -13,6 +14,19 @@ type Response struct {
Data interface{} `json:"data"`
}

func (resp *Response) DecodeData(out interface{}) error {
// Decode generically unmarshaled JSON (map[string]any, []any) into a typed struct
// honoring `json` tags and allowing weak type conversions.
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
TagName: "json",
Result: out,
})
if err != nil {
return err
}
return dec.Decode(resp.Data)
}

// RenderJSON renders response with json
func RenderJSON(ctx *gin.Context, errCode int, err error, data interface{}) {
var errMsg string
Expand Down
34 changes: 34 additions & 0 deletions coordinator/conf/config_proxy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"proxy_manager": {
"proxy_cli": {
"proxy_name": "proxy_name",
"secret": "client private key"
},
"auth": {
"secret": "proxy secret key",
"challenge_expire_duration_sec": 3600,
"login_expire_duration_sec": 3600
},
"verifier": {
"min_prover_version": "v4.4.45",
"verifiers": [
{
"assets_path": "assets",
"fork_name": "euclidV2"
},
{
"assets_path": "assets",
"fork_name": "feynman"
}
]
}
},
"coordinators": {
"sepolia": {
"base_url": "http://localhost:8555",
"retry_count": 10,
"retry_wait_time_sec": 10,
"connection_timeout_sec": 30
}
}
}
71 changes: 71 additions & 0 deletions coordinator/internal/config/proxy_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package config

import (
"encoding/json"
"os"
"path/filepath"

"scroll-tech/common/utils"
)

// Proxy loads proxy configuration items.
type ProxyManager struct {
// Zk verifier config help to confine the connected prover.
Verifier *VerifierConfig `json:"verifier"`
Client *ProxyClient `json:"proxy_cli"`
Auth *Auth `json:"auth"`
}

func (m *ProxyManager) Normalize() {
if m.Client.Secret == "" {
m.Client.Secret = m.Auth.Secret
}

if m.Client.ProxyVersion == "" {
m.Client.ProxyVersion = m.Verifier.MinProverVersion
}
}

// Proxy client configuration for connect to upstream as a client
type ProxyClient struct {
ProxyName string `json:"proxy_name"`
ProxyVersion string `json:"proxy_version,omitempty"`
Secret string `json:"secret,omitempty"`
}

// Coordinator configuration
type UpStream struct {
BaseUrl string `json:"base_url"`
RetryCount uint `json:"retry_count"`
RetryWaitTime uint `json:"retry_wait_time_sec"`
ConnectionTimeoutSec uint `json:"connection_timeout_sec"`
}

// Config load configuration items.
type ProxyConfig struct {
ProxyManager *ProxyManager `json:"proxy_manager"`
ProxyName string `json:"proxy_name"`
Coordinators map[string]*UpStream `json:"coondiators"`
}

// NewConfig returns a new instance of Config.
func NewProxyConfig(file string) (*ProxyConfig, error) {
buf, err := os.ReadFile(filepath.Clean(file))
if err != nil {
return nil, err
}

cfg := &ProxyConfig{}
err = json.Unmarshal(buf, cfg)
if err != nil {
return nil, err
}

// Override config with environment variables
err = utils.OverrideConfigWithEnv(cfg, "SCROLL_COORDINATOR_PROXY")
if err != nil {
return nil, err
}

return cfg, nil
}
57 changes: 40 additions & 17 deletions coordinator/internal/controller/api/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,56 @@ type AuthController struct {
loginLogic *auth.LoginLogic
}

func NewAuthControllerWithLogic(loginLogic *auth.LoginLogic) *AuthController {
return &AuthController{
loginLogic: loginLogic,
}
}

// NewAuthController returns an LoginController instance
func NewAuthController(db *gorm.DB, cfg *config.Config, vf *verifier.Verifier) *AuthController {
return &AuthController{
loginLogic: auth.NewLoginLogic(db, cfg, vf),
loginLogic: auth.NewLoginLogic(db, cfg.ProverManager.Verifier, vf),
}
}

// Login the api controller for login
// Login the api controller for login, used as the Authenticator in JWT
// It can work in two mode: full process for normal login, or if login request
// is posted from proxy, run a simpler process to login a client
func (a *AuthController) Login(c *gin.Context) (interface{}, error) {

// check if the login is post by proxy
var viaProxy bool
if proverType, proverTypeExist := c.Get(types.ProverProviderTypeKey); proverTypeExist {
proverType := uint8(proverType.(float64))
viaProxy = proverType == types.ProverProviderTypeProxy
}

var login types.LoginParameter
if err := c.ShouldBind(&login); err != nil {
return "", fmt.Errorf("missing the public_key, err:%w", err)
}

// check login parameter's token is equal to bearer token, the Authorization must be existed
// if not exist, the jwt token will intercept it
brearToken := c.GetHeader("Authorization")
if brearToken != "Bearer "+login.Message.Challenge {
return "", errors.New("check challenge failure for the not equal challenge string")
// if not, process with normal login
if !viaProxy {
// check login parameter's token is equal to bearer token, the Authorization must be existed
// if not exist, the jwt token will intercept it
brearToken := c.GetHeader("Authorization")
if brearToken != "Bearer "+login.Message.Challenge {
return "", errors.New("check challenge failure for the not equal challenge string")
}

if err := auth.VerifyMsg(&login); err != nil {
return "", err
}

// check the challenge is used, if used, return failure
if err := a.loginLogic.InsertChallengeString(c, login.Message.Challenge); err != nil {
return "", fmt.Errorf("login insert challenge string failure:%w", err)
}
}

if err := a.loginLogic.Check(&login); err != nil {
if err := a.loginLogic.CompatiblityCheck(&login); err != nil {
return "", fmt.Errorf("check the login parameter failure: %w", err)
}

Expand All @@ -49,11 +77,6 @@ func (a *AuthController) Login(c *gin.Context) (interface{}, error) {
return "", fmt.Errorf("prover hard fork name failure:%w", err)
}

// check the challenge is used, if used, return failure
if err := a.loginLogic.InsertChallengeString(c, login.Message.Challenge); err != nil {
return "", fmt.Errorf("login insert challenge string failure:%w", err)
}

returnData := types.LoginParameterWithHardForkName{
HardForkName: hardForkNames,
LoginParameter: login,
Expand Down Expand Up @@ -85,10 +108,6 @@ func (a *AuthController) IdentityHandler(c *gin.Context) interface{} {
c.Set(types.ProverName, proverName)
}

if publicKey, ok := claims[types.PublicKey]; ok {
c.Set(types.PublicKey, publicKey)
}

if proverVersion, ok := claims[types.ProverVersion]; ok {
c.Set(types.ProverVersion, proverVersion)
}
Expand All @@ -101,5 +120,9 @@ func (a *AuthController) IdentityHandler(c *gin.Context) interface{} {
c.Set(types.ProverProviderTypeKey, providerType)
}

if publicKey, ok := claims[types.PublicKey]; ok {
return publicKey
}

return nil
}
137 changes: 137 additions & 0 deletions coordinator/internal/controller/proxy/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package proxy

import (
"fmt"

"time"

jwt "github.com/appleboy/gin-jwt/v2"
"github.com/gin-gonic/gin"
"github.com/scroll-tech/go-ethereum/log"

"scroll-tech/coordinator/internal/config"
"scroll-tech/coordinator/internal/controller/api"
"scroll-tech/coordinator/internal/logic/auth"
"scroll-tech/coordinator/internal/logic/verifier"
"scroll-tech/coordinator/internal/types"
)

// AuthController is login API
type AuthController struct {
apiLogin *api.AuthController
clients Clients
proverMgr *ProverManager
}

const upstreamConnTimeout = time.Second * 2
const LoginParamCache = "login_param"
const ProverTypesKey = "prover_types"
const SignatureKey = "prover_signature"

// NewAuthController returns an LoginController instance
func NewAuthController(cfg *config.ProxyConfig, clients Clients, proverMgr *ProverManager) *AuthController {

// use a dummy Verifier to create login logic (we do not use any information in verifier)
dummyVf := verifier.Verifier{
OpenVMVkMap: make(map[string]struct{}),
}
loginLogic := auth.NewLoginLogicWithSimpleDeduplicator(cfg.ProxyManager.Verifier, &dummyVf)

authController := &AuthController{
apiLogin: api.NewAuthControllerWithLogic(loginLogic),
clients: clients,
proverMgr: proverMgr,
}

return authController
}

// Login extended the Login hander in api controller
func (a *AuthController) Login(c *gin.Context) (interface{}, error) {

loginRes, err := a.apiLogin.Login(c)
if err != nil {
return nil, err
}
loginParam := loginRes.(types.LoginParameterWithHardForkName)

if loginParam.LoginParameter.Message.ProverProviderType == types.ProverProviderTypeProxy {
return nil, fmt.Errorf("proxy do not support recursive login")
}

session := a.proverMgr.GetOrCreate(loginParam.PublicKey)

for n, cli := range a.clients {

go func(n string, cli Client) {
if err := session.ProxyLogin(c, cli, n, &loginParam.LoginParameter); err != nil {
log.Error("proxy login failed during token cache update",
"userKey", loginParam.PublicKey,
"upstream", n,
"error", err)
}
}(n, cli)
}

return loginParam.LoginParameter, nil
}

// PayloadFunc returns jwt.MapClaims with {public key, prover name}.
func (a *AuthController) PayloadFunc(data interface{}) jwt.MapClaims {
v, ok := data.(types.LoginParameter)
if !ok {
return jwt.MapClaims{}
}

return jwt.MapClaims{
types.PublicKey: v.PublicKey,
types.ProverName: v.Message.ProverName,
types.ProverVersion: v.Message.ProverVersion,
types.ProverProviderTypeKey: v.Message.ProverProviderType,
SignatureKey: v.Signature,
ProverTypesKey: v.Message.ProverTypes,
}
}

// IdentityHandler replies to client for /login
func (a *AuthController) IdentityHandler(c *gin.Context) interface{} {
claims := jwt.ExtractClaims(c)
loginParam := &types.LoginParameter{}

if proverName, ok := claims[types.ProverName]; ok {
loginParam.Message.ProverName, _ = proverName.(string)
}

if proverVersion, ok := claims[types.ProverVersion]; ok {
loginParam.Message.ProverVersion, _ = proverVersion.(string)
}

if providerType, ok := claims[types.ProverProviderTypeKey]; ok {
num, _ := providerType.(float64)
loginParam.Message.ProverProviderType = types.ProverProviderType(num)
}

if signature, ok := claims[SignatureKey]; ok {
loginParam.Signature, _ = signature.(string)
}

if proverTypes, ok := claims[ProverTypesKey]; ok {
arr, _ := proverTypes.([]any)
for _, elm := range arr {
num, _ := elm.(float64)
loginParam.Message.ProverTypes = append(loginParam.Message.ProverTypes, types.ProverType(num))
}
}

if publicKey, ok := claims[types.PublicKey]; ok {
loginParam.PublicKey, _ = publicKey.(string)
}

if loginParam.PublicKey != "" {

c.Set(LoginParamCache, loginParam)
return loginParam.PublicKey
}

return nil
}
Loading