code.vegaprotocol.io/vega@v0.79.0/wallet/service/v1/endpoints.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package v1
    17  
    18  import (
    19  	"encoding/base64"
    20  	"encoding/hex"
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"io"
    25  	"net/http"
    26  	"sort"
    27  	"time"
    28  
    29  	"code.vegaprotocol.io/vega/commands"
    30  	vfmt "code.vegaprotocol.io/vega/libs/fmt"
    31  	vgrand "code.vegaprotocol.io/vega/libs/rand"
    32  	api "code.vegaprotocol.io/vega/protos/vega/api/v1"
    33  	commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1"
    34  	walletpb "code.vegaprotocol.io/vega/protos/vega/wallet/v1"
    35  	"code.vegaprotocol.io/vega/version"
    36  	nodetypes "code.vegaprotocol.io/vega/wallet/api/node/types"
    37  	wcommands "code.vegaprotocol.io/vega/wallet/commands"
    38  	"code.vegaprotocol.io/vega/wallet/network"
    39  	"code.vegaprotocol.io/vega/wallet/wallet"
    40  
    41  	"github.com/golang/protobuf/jsonpb"
    42  	"github.com/julienschmidt/httprouter"
    43  	"go.uber.org/zap"
    44  )
    45  
    46  const (
    47  	TxnValidationFailure   uint32 = 51
    48  	TxnDecodingFailure     uint32 = 60
    49  	TxnInternalError       uint32 = 70
    50  	TxnUnknownCommandError uint32 = 80
    51  	TxnSpamError           uint32 = 89
    52  )
    53  
    54  // CreateWalletRequest describes the request for CreateWallet.
    55  type CreateWalletRequest struct {
    56  	Wallet     string `json:"wallet"`
    57  	Passphrase string `json:"passphrase"`
    58  }
    59  
    60  const TXIDLENGTH = 20
    61  
    62  func ParseCreateWalletRequest(r *http.Request) (*CreateWalletRequest, commands.Errors) {
    63  	errs := commands.NewErrors()
    64  
    65  	req := &CreateWalletRequest{}
    66  	if err := unmarshalBody(r, &req); err != nil {
    67  		return nil, errs.FinalAdd(err)
    68  	}
    69  
    70  	if len(req.Wallet) == 0 {
    71  		errs.AddForProperty("wallet", commands.ErrIsRequired)
    72  	}
    73  
    74  	if len(req.Passphrase) == 0 {
    75  		errs.AddForProperty("passphrase", commands.ErrIsRequired)
    76  	}
    77  
    78  	if !errs.Empty() {
    79  		return nil, errs
    80  	}
    81  
    82  	return req, errs
    83  }
    84  
    85  // CreateWalletResponse returns the authentication token and the auto-generated
    86  // recovery phrase of the created wallet.
    87  type CreateWalletResponse struct {
    88  	RecoveryPhrase string `json:"recoveryPhrase"`
    89  	Token          string `json:"token"`
    90  }
    91  
    92  // ImportWalletRequest describes the request for ImportWallet.
    93  type ImportWalletRequest struct {
    94  	Wallet         string `json:"wallet"`
    95  	Passphrase     string `json:"passphrase"`
    96  	RecoveryPhrase string `json:"recoveryPhrase"`
    97  	Version        uint32 `json:"version"`
    98  }
    99  
   100  func ParseImportWalletRequest(r *http.Request) (*ImportWalletRequest, commands.Errors) {
   101  	errs := commands.NewErrors()
   102  
   103  	req := &ImportWalletRequest{}
   104  	if err := unmarshalBody(r, &req); err != nil {
   105  		return nil, errs.FinalAdd(err)
   106  	}
   107  
   108  	if len(req.Wallet) == 0 {
   109  		errs.AddForProperty("wallet", commands.ErrIsRequired)
   110  	}
   111  
   112  	if len(req.Passphrase) == 0 {
   113  		errs.AddForProperty("passphrase", commands.ErrIsRequired)
   114  	}
   115  
   116  	if len(req.RecoveryPhrase) == 0 {
   117  		errs.AddForProperty("recoveryPhrase", commands.ErrIsRequired)
   118  	}
   119  
   120  	if req.Version == 0 {
   121  		req.Version = wallet.LatestVersion
   122  	}
   123  
   124  	if !errs.Empty() {
   125  		return nil, errs
   126  	}
   127  
   128  	return req, errs
   129  }
   130  
   131  // LoginWalletRequest describes the request for CreateWallet, LoginWallet.
   132  type LoginWalletRequest struct {
   133  	Wallet     string `json:"wallet"`
   134  	Passphrase string `json:"passphrase"`
   135  }
   136  
   137  func ParseLoginWalletRequest(r *http.Request) (*LoginWalletRequest, commands.Errors) {
   138  	errs := commands.NewErrors()
   139  
   140  	req := &LoginWalletRequest{}
   141  	if err := unmarshalBody(r, &req); err != nil {
   142  		return nil, errs.FinalAdd(err)
   143  	}
   144  
   145  	if len(req.Wallet) == 0 {
   146  		errs.AddForProperty("wallet", commands.ErrIsRequired)
   147  	}
   148  
   149  	if len(req.Passphrase) == 0 {
   150  		errs.AddForProperty("passphrase", commands.ErrIsRequired)
   151  	}
   152  
   153  	if !errs.Empty() {
   154  		return nil, errs
   155  	}
   156  
   157  	return req, errs
   158  }
   159  
   160  // TaintKeyRequest describes the request for TaintKey.
   161  type TaintKeyRequest struct {
   162  	Passphrase string `json:"passphrase"`
   163  }
   164  
   165  func ParseTaintKeyRequest(r *http.Request, keyID string) (*TaintKeyRequest, commands.Errors) {
   166  	errs := commands.NewErrors()
   167  
   168  	if len(keyID) == 0 {
   169  		errs.AddForProperty("keyid", commands.ErrIsRequired)
   170  	}
   171  
   172  	req := &TaintKeyRequest{}
   173  	if err := unmarshalBody(r, &req); err != nil {
   174  		return nil, errs.FinalAdd(err)
   175  	}
   176  
   177  	if len(req.Passphrase) == 0 {
   178  		errs.AddForProperty("passphrase", commands.ErrIsRequired)
   179  	}
   180  
   181  	if !errs.Empty() {
   182  		return nil, errs
   183  	}
   184  
   185  	return req, errs
   186  }
   187  
   188  // GenKeyPairRequest describes the request for GenerateKeyPair.
   189  type GenKeyPairRequest struct {
   190  	Passphrase string            `json:"passphrase"`
   191  	Meta       []wallet.Metadata `json:"meta"`
   192  }
   193  
   194  func ParseGenKeyPairRequest(r *http.Request) (*GenKeyPairRequest, commands.Errors) {
   195  	errs := commands.NewErrors()
   196  
   197  	req := &GenKeyPairRequest{}
   198  	if err := unmarshalBody(r, &req); err != nil {
   199  		return nil, errs.FinalAdd(err)
   200  	}
   201  
   202  	if len(req.Passphrase) == 0 {
   203  		errs.AddForProperty("passphrase", commands.ErrIsRequired)
   204  	}
   205  
   206  	if !errs.Empty() {
   207  		return nil, errs
   208  	}
   209  
   210  	return req, errs
   211  }
   212  
   213  // UpdateMetaRequest describes the request for UpdateMetadata.
   214  type UpdateMetaRequest struct {
   215  	Passphrase string            `json:"passphrase"`
   216  	Meta       []wallet.Metadata `json:"meta"`
   217  }
   218  
   219  func ParseUpdateMetaRequest(r *http.Request, keyID string) (*UpdateMetaRequest, commands.Errors) {
   220  	errs := commands.NewErrors()
   221  
   222  	if len(keyID) == 0 {
   223  		errs.AddForProperty("keyid", commands.ErrIsRequired)
   224  	}
   225  
   226  	req := &UpdateMetaRequest{}
   227  	if err := unmarshalBody(r, &req); err != nil {
   228  		return nil, errs.FinalAdd(err)
   229  	}
   230  
   231  	if len(req.Passphrase) == 0 {
   232  		errs.AddForProperty("passphrase", commands.ErrIsRequired)
   233  	}
   234  
   235  	if !errs.Empty() {
   236  		return nil, errs
   237  	}
   238  
   239  	return req, errs
   240  }
   241  
   242  // SignAnyRequest describes the request for SignAny.
   243  type SignAnyRequest struct {
   244  	// InputData is the payload to generate a signature from. I should be
   245  	// base 64 encoded.
   246  	InputData string `json:"inputData"`
   247  	// PubKey is used to retrieve the private key to sign the InputDate.
   248  	PubKey string `json:"pubKey"`
   249  
   250  	decodedInputData []byte
   251  }
   252  
   253  func ParseSignAnyRequest(r *http.Request) (*SignAnyRequest, commands.Errors) {
   254  	errs := commands.NewErrors()
   255  
   256  	req := &SignAnyRequest{}
   257  	if err := unmarshalBody(r, &req); err != nil {
   258  		return nil, errs.FinalAdd(err)
   259  	}
   260  
   261  	if len(req.InputData) == 0 {
   262  		errs.AddForProperty("inputData", commands.ErrIsRequired)
   263  	}
   264  	decodedInputData, err := base64.StdEncoding.DecodeString(req.InputData)
   265  	if err != nil {
   266  		errs.AddForProperty("inputData", ErrShouldBeBase64Encoded)
   267  	} else {
   268  		req.decodedInputData = decodedInputData
   269  	}
   270  
   271  	if len(req.PubKey) == 0 {
   272  		errs.AddForProperty("pubKey", commands.ErrIsRequired)
   273  	}
   274  
   275  	if !errs.Empty() {
   276  		return nil, errs
   277  	}
   278  
   279  	return req, errs
   280  }
   281  
   282  // VerifyAnyRequest describes the request for VerifyAny.
   283  type VerifyAnyRequest struct {
   284  	// InputData is the payload to be verified. It should be base64 encoded.
   285  	InputData string `json:"inputData"`
   286  	// Signature is the signature to check against the InputData. It should be
   287  	// base64 encoded.
   288  	Signature string `json:"signature"`
   289  	// PubKey is the public key used along the signature to check the InputData.
   290  	PubKey string `json:"pubKey"`
   291  
   292  	decodedInputData []byte
   293  	decodedSignature []byte
   294  }
   295  
   296  func ParseVerifyAnyRequest(r *http.Request) (*VerifyAnyRequest, commands.Errors) {
   297  	errs := commands.NewErrors()
   298  
   299  	req := &VerifyAnyRequest{}
   300  	if err := unmarshalBody(r, &req); err != nil {
   301  		return nil, errs.FinalAdd(err)
   302  	}
   303  
   304  	if len(req.InputData) == 0 {
   305  		errs.AddForProperty("inputData", commands.ErrIsRequired)
   306  	} else {
   307  		decodedInputData, err := base64.StdEncoding.DecodeString(req.InputData)
   308  		if err != nil {
   309  			errs.AddForProperty("inputData", ErrShouldBeBase64Encoded)
   310  		} else {
   311  			req.decodedInputData = decodedInputData
   312  		}
   313  	}
   314  
   315  	if len(req.Signature) == 0 {
   316  		errs.AddForProperty("signature", commands.ErrIsRequired)
   317  	} else {
   318  		decodedSignature, err := base64.StdEncoding.DecodeString(req.Signature)
   319  		if err != nil {
   320  			errs.AddForProperty("signature", ErrShouldBeBase64Encoded)
   321  		} else {
   322  			req.decodedSignature = decodedSignature
   323  		}
   324  	}
   325  
   326  	if len(req.PubKey) == 0 {
   327  		errs.AddForProperty("pubKey", commands.ErrIsRequired)
   328  	}
   329  
   330  	if !errs.Empty() {
   331  		return nil, errs
   332  	}
   333  
   334  	return req, nil
   335  }
   336  
   337  func ParseSubmitTransactionRequest(r *http.Request) (*walletpb.SubmitTransactionRequest, commands.Errors) {
   338  	errs := commands.NewErrors()
   339  
   340  	req := &walletpb.SubmitTransactionRequest{
   341  		Propagate: true,
   342  	}
   343  	if err := jsonpb.Unmarshal(r.Body, req); err != nil {
   344  		return nil, errs.FinalAdd(err)
   345  	}
   346  
   347  	if errs = wcommands.CheckSubmitTransactionRequest(req); !errs.Empty() {
   348  		return nil, errs
   349  	}
   350  
   351  	return req, nil
   352  }
   353  
   354  // KeyResponse describes the response to a request that returns a single key.
   355  type KeyResponse struct {
   356  	Key KeyKeyResponse `json:"key"`
   357  }
   358  
   359  type KeyKeyResponse struct {
   360  	Idx          uint32            `json:"index"`
   361  	PublicKey    string            `json:"pub"`
   362  	KeyName      string            `json:"name"`
   363  	Algorithm    wallet.Algorithm  `json:"algorithm"`
   364  	Tainted      bool              `json:"tainted"`
   365  	MetadataList []wallet.Metadata `json:"meta"`
   366  }
   367  
   368  // KeysResponse describes the response to a request that returns a list of keys.
   369  type KeysResponse struct {
   370  	Keys []KeyKeyResponse `json:"keys"`
   371  }
   372  
   373  // SignAnyResponse describes the response for SignAny.
   374  type SignAnyResponse struct {
   375  	HexSignature    string `json:"hexSignature"`
   376  	Base64Signature string `json:"base64Signature"`
   377  }
   378  
   379  // VerifyAnyResponse describes the response for VerifyAny.
   380  type VerifyAnyResponse struct {
   381  	Valid bool `json:"success"`
   382  }
   383  
   384  // SuccessResponse describes the response to a request that returns a simple true/false answer.
   385  type SuccessResponse struct {
   386  	Success bool `json:"success"`
   387  }
   388  
   389  // TokenResponse describes the response to a request that returns a token.
   390  type TokenResponse struct {
   391  	Token string `json:"token"`
   392  }
   393  
   394  // VersionResponse describes the response to a request that returns app version info.
   395  type VersionResponse struct {
   396  	Version     string `json:"version"`
   397  	VersionHash string `json:"versionHash"`
   398  }
   399  
   400  // NetworkResponse describes the response to a request that returns app hosts info.
   401  type NetworkResponse struct {
   402  	Network network.Network `json:"network"`
   403  }
   404  
   405  func (s *API) CreateWallet(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
   406  	req, errs := ParseCreateWalletRequest(r)
   407  	if !errs.Empty() {
   408  		s.writeBadRequest(w, errs)
   409  		return
   410  	}
   411  
   412  	recoveryPhrase, err := s.handler.CreateWallet(req.Wallet, req.Passphrase)
   413  	if err != nil {
   414  		s.writeBadRequestErr(w, err)
   415  		return
   416  	}
   417  
   418  	token, err := s.auth.NewSession(req.Wallet)
   419  	if err != nil {
   420  		s.writeInternalError(w, err)
   421  		return
   422  	}
   423  
   424  	s.writeSuccess(w, CreateWalletResponse{
   425  		RecoveryPhrase: recoveryPhrase,
   426  		Token:          token,
   427  	})
   428  }
   429  
   430  func (s *API) ImportWallet(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
   431  	req, errs := ParseImportWalletRequest(r)
   432  	if !errs.Empty() {
   433  		s.writeBadRequest(w, errs)
   434  		return
   435  	}
   436  
   437  	err := s.handler.ImportWallet(req.Wallet, req.Passphrase, req.RecoveryPhrase, req.Version)
   438  	if err != nil {
   439  		s.writeBadRequestErr(w, err)
   440  		return
   441  	}
   442  
   443  	token, err := s.auth.NewSession(req.Wallet)
   444  	if err != nil {
   445  		s.writeInternalError(w, err)
   446  		return
   447  	}
   448  
   449  	s.writeSuccess(w, TokenResponse{Token: token})
   450  }
   451  
   452  func (s *API) Login(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
   453  	req, errs := ParseLoginWalletRequest(r)
   454  	if !errs.Empty() {
   455  		s.writeBadRequest(w, errs)
   456  		return
   457  	}
   458  
   459  	err := s.handler.LoginWallet(req.Wallet, req.Passphrase)
   460  	if err != nil {
   461  		s.writeForbiddenError(w, err)
   462  		return
   463  	}
   464  
   465  	token, err := s.auth.NewSession(req.Wallet)
   466  	if err != nil {
   467  		s.writeInternalError(w, err)
   468  		return
   469  	}
   470  
   471  	s.writeSuccess(w, TokenResponse{Token: token})
   472  }
   473  
   474  func (s *API) Revoke(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
   475  	token, err := extractToken(r)
   476  	if err != nil {
   477  		writeError(w, err)
   478  		return
   479  	}
   480  
   481  	if _, err := s.auth.Revoke(token); err != nil {
   482  		s.writeForbiddenError(w, err)
   483  		return
   484  	}
   485  
   486  	s.writeSuccess(w, nil)
   487  }
   488  
   489  func (s *API) GenerateKeyPair(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
   490  	token, err := extractToken(r)
   491  	if err != nil {
   492  		writeError(w, err)
   493  		return
   494  	}
   495  
   496  	req, errs := ParseGenKeyPairRequest(r)
   497  	if !errs.Empty() {
   498  		s.writeBadRequest(w, errs)
   499  		return
   500  	}
   501  
   502  	name, err := s.auth.VerifyToken(token)
   503  	if err != nil {
   504  		s.writeForbiddenError(w, err)
   505  		return
   506  	}
   507  
   508  	pubKey, err := s.handler.SecureGenerateKeyPair(name, req.Passphrase, req.Meta)
   509  	if err != nil {
   510  		if errors.Is(err, wallet.ErrWrongPassphrase) {
   511  			s.writeForbiddenError(w, err)
   512  		} else {
   513  			s.writeInternalError(w, err)
   514  		}
   515  		return
   516  	}
   517  
   518  	key, err := s.handler.GetPublicKey(name, pubKey)
   519  	if err != nil {
   520  		s.writeInternalError(w, err)
   521  		return
   522  	}
   523  
   524  	s.writeSuccess(w, KeyResponse{
   525  		Key: KeyKeyResponse{
   526  			Idx:       key.Index(),
   527  			PublicKey: key.Key(),
   528  			KeyName:   key.Name(),
   529  			Algorithm: wallet.Algorithm{
   530  				Name:    key.AlgorithmName(),
   531  				Version: key.AlgorithmVersion(),
   532  			},
   533  			Tainted:      key.IsTainted(),
   534  			MetadataList: key.Metadata(),
   535  		},
   536  	})
   537  }
   538  
   539  func (s *API) GetPublicKey(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
   540  	token, err := extractToken(r)
   541  	if err != nil {
   542  		writeError(w, err)
   543  		return
   544  	}
   545  
   546  	name, err := s.auth.VerifyToken(token)
   547  	if err != nil {
   548  		s.writeForbiddenError(w, err)
   549  		return
   550  	}
   551  
   552  	key, err := s.handler.GetPublicKey(name, ps.ByName("keyid"))
   553  	if err != nil {
   554  		var statusCode int
   555  		if errors.Is(err, wallet.ErrPubKeyDoesNotExist) {
   556  			statusCode = http.StatusNotFound
   557  		} else {
   558  			statusCode = http.StatusInternalServerError
   559  		}
   560  		s.writeError(w, newErrorResponse(err.Error()), statusCode)
   561  		return
   562  	}
   563  
   564  	s.writeSuccess(w, KeyResponse{
   565  		Key: KeyKeyResponse{
   566  			Idx:       key.Index(),
   567  			PublicKey: key.Key(),
   568  			KeyName:   key.Name(),
   569  			Algorithm: wallet.Algorithm{
   570  				Name:    key.AlgorithmName(),
   571  				Version: key.AlgorithmVersion(),
   572  			},
   573  			Tainted:      key.IsTainted(),
   574  			MetadataList: key.Metadata(),
   575  		},
   576  	})
   577  }
   578  
   579  func (s *API) ListPublicKeys(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
   580  	token, err := extractToken(r)
   581  	if err != nil {
   582  		writeError(w, err)
   583  		return
   584  	}
   585  
   586  	name, err := s.auth.VerifyToken(token)
   587  	if err != nil {
   588  		s.writeForbiddenError(w, err)
   589  		return
   590  	}
   591  
   592  	keys, err := s.handler.ListPublicKeys(name)
   593  	if err != nil {
   594  		s.writeInternalError(w, err)
   595  		return
   596  	}
   597  
   598  	res := make([]KeyKeyResponse, 0, len(keys))
   599  	for _, key := range keys {
   600  		res = append(res, KeyKeyResponse{
   601  			Idx:       key.Index(),
   602  			PublicKey: key.Key(),
   603  			KeyName:   key.Name(),
   604  			Algorithm: wallet.Algorithm{
   605  				Name:    key.AlgorithmName(),
   606  				Version: key.AlgorithmVersion(),
   607  			},
   608  			Tainted:      key.IsTainted(),
   609  			MetadataList: key.Metadata(),
   610  		})
   611  	}
   612  
   613  	s.writeSuccess(w, KeysResponse{Keys: res})
   614  }
   615  
   616  func (s *API) TaintKey(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
   617  	token, err := extractToken(r)
   618  	if err != nil {
   619  		writeError(w, err)
   620  		return
   621  	}
   622  
   623  	keyID := ps.ByName("keyid")
   624  	req, errs := ParseTaintKeyRequest(r, keyID)
   625  	if !errs.Empty() {
   626  		s.writeBadRequest(w, errs)
   627  		return
   628  	}
   629  
   630  	name, err := s.auth.VerifyToken(token)
   631  	if err != nil {
   632  		s.writeForbiddenError(w, err)
   633  		return
   634  	}
   635  
   636  	if err = s.handler.TaintKey(name, keyID, req.Passphrase); err != nil {
   637  		if errors.Is(err, wallet.ErrWrongPassphrase) {
   638  			s.writeForbiddenError(w, err)
   639  		} else {
   640  			s.writeInternalError(w, err)
   641  		}
   642  		return
   643  	}
   644  
   645  	s.writeSuccess(w, nil)
   646  }
   647  
   648  func (s *API) UpdateMeta(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
   649  	token, err := extractToken(r)
   650  	if err != nil {
   651  		writeError(w, err)
   652  		return
   653  	}
   654  
   655  	keyID := ps.ByName("keyid")
   656  	req, errs := ParseUpdateMetaRequest(r, keyID)
   657  	if !errs.Empty() {
   658  		s.writeBadRequest(w, errs)
   659  		return
   660  	}
   661  
   662  	name, err := s.auth.VerifyToken(token)
   663  	if err != nil {
   664  		s.writeForbiddenError(w, err)
   665  		return
   666  	}
   667  
   668  	if err = s.handler.UpdateMeta(name, keyID, req.Passphrase, req.Meta); err != nil {
   669  		if errors.Is(err, wallet.ErrWrongPassphrase) {
   670  			s.writeForbiddenError(w, err)
   671  		} else {
   672  			s.writeInternalError(w, err)
   673  		}
   674  		return
   675  	}
   676  
   677  	s.writeSuccess(w, nil)
   678  }
   679  
   680  func (s *API) SignAny(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
   681  	token, err := extractToken(r)
   682  	if err != nil {
   683  		writeError(w, err)
   684  		return
   685  	}
   686  
   687  	req, errs := ParseSignAnyRequest(r)
   688  	if !errs.Empty() {
   689  		s.writeBadRequest(w, errs)
   690  		return
   691  	}
   692  
   693  	name, err := s.auth.VerifyToken(token)
   694  	if err != nil {
   695  		s.writeForbiddenError(w, err)
   696  		return
   697  	}
   698  
   699  	signature, err := s.handler.SignAny(name, req.decodedInputData, req.PubKey)
   700  	if err != nil {
   701  		s.writeInternalError(w, err)
   702  		return
   703  	}
   704  
   705  	res := SignAnyResponse{
   706  		HexSignature:    hex.EncodeToString(signature),
   707  		Base64Signature: base64.StdEncoding.EncodeToString(signature),
   708  	}
   709  
   710  	s.writeSuccess(w, res)
   711  }
   712  
   713  func (s *API) VerifyAny(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
   714  	req, errs := ParseVerifyAnyRequest(r)
   715  	if !errs.Empty() {
   716  		s.writeBadRequest(w, errs)
   717  		return
   718  	}
   719  
   720  	verified, err := s.handler.VerifyAny(req.decodedInputData, req.decodedSignature, req.PubKey)
   721  	if err != nil {
   722  		s.writeInternalError(w, err)
   723  		return
   724  	}
   725  
   726  	s.writeSuccess(w, VerifyAnyResponse{Valid: verified})
   727  }
   728  
   729  func (s *API) CheckTx(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
   730  	defer r.Body.Close()
   731  
   732  	token, err := extractToken(r)
   733  	if err != nil {
   734  		writeError(w, err)
   735  		return
   736  	}
   737  
   738  	name, err := s.auth.VerifyToken(token)
   739  	if err != nil {
   740  		s.writeForbiddenError(w, err)
   741  		return
   742  	}
   743  
   744  	req, errs := ParseSubmitTransactionRequest(r)
   745  	if !errs.Empty() {
   746  		s.writeBadRequest(w, errs)
   747  		return
   748  	}
   749  
   750  	stats, cltIdx, err := s.nodeForward.SpamStatistics(r.Context(), req.PubKey)
   751  	if err != nil {
   752  		s.writeInternalError(w, ErrCouldNotGetBlockHeight)
   753  		return
   754  	}
   755  
   756  	if stats.ChainId == "" {
   757  		s.writeInternalError(w, ErrCouldNotGetChainID)
   758  		return
   759  	}
   760  	st := convertSpamStatistics(stats)
   761  	tx, err := s.handler.SignTx(name, req, st.LastBlockHeight, stats.ChainId)
   762  	if err != nil {
   763  		s.writeInternalError(w, err)
   764  		return
   765  	}
   766  
   767  	// generate proof of work for the transaction
   768  	tx.Pow, err = s.spam.GenerateProofOfWork(req.PubKey, st)
   769  	if err != nil {
   770  		s.writeInternalError(w, err)
   771  		return
   772  	}
   773  
   774  	result, err := s.nodeForward.CheckTx(r.Context(), tx, cltIdx)
   775  	if err != nil {
   776  		s.writeInternalError(w, err)
   777  		return
   778  	}
   779  
   780  	s.writeSuccess(w, struct {
   781  		Success   bool                    `json:"success"`
   782  		Code      uint32                  `json:"code"`
   783  		GasWanted int64                   `json:"gas_wanted"`
   784  		GasUsed   int64                   `json:"gas_used"`
   785  		Tx        *commandspb.Transaction `json:"tx"`
   786  	}{
   787  		Success:   result.Success,
   788  		Code:      result.Code,
   789  		GasWanted: result.GasWanted,
   790  		GasUsed:   result.GasUsed,
   791  		Tx:        tx,
   792  	})
   793  }
   794  
   795  func (s *API) SignTxSync(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
   796  	s.signTx(w, r, p, api.SubmitTransactionRequest_TYPE_SYNC)
   797  }
   798  
   799  func (s *API) SignTxCommit(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
   800  	s.signTx(w, r, p, api.SubmitTransactionRequest_TYPE_COMMIT)
   801  }
   802  
   803  func (s *API) SignTx(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
   804  	s.signTx(w, r, p, api.SubmitTransactionRequest_TYPE_ASYNC)
   805  }
   806  
   807  func (s *API) signTx(w http.ResponseWriter, r *http.Request, _ httprouter.Params, ty api.SubmitTransactionRequest_Type) {
   808  	defer r.Body.Close()
   809  
   810  	token, err := extractToken(r)
   811  	if err != nil {
   812  		writeError(w, err)
   813  		return
   814  	}
   815  
   816  	name, err := s.auth.VerifyToken(token)
   817  	if err != nil {
   818  		s.writeForbiddenError(w, err)
   819  		return
   820  	}
   821  
   822  	req, errs := ParseSubmitTransactionRequest(r)
   823  	if !errs.Empty() {
   824  		s.writeBadRequest(w, errs)
   825  		return
   826  	}
   827  
   828  	txID := vgrand.RandomStr(TXIDLENGTH)
   829  	receivedAt := time.Now()
   830  	approved, err := s.policy.Ask(req, txID, receivedAt)
   831  	if err != nil {
   832  		s.log.Error("couldn't get user consent", zap.Error(err))
   833  		s.writeError(w, err, http.StatusServiceUnavailable)
   834  		return
   835  	}
   836  
   837  	if !approved {
   838  		s.log.Info("user rejected transaction signing request", zap.Any("request", req))
   839  		s.writeError(w, ErrRejectedSignRequest, http.StatusUnauthorized)
   840  		return
   841  	}
   842  	s.log.Info("user approved transaction signing request", zap.Any("request", req))
   843  
   844  	stats, cltIdx, err := s.nodeForward.SpamStatistics(r.Context(), req.PubKey)
   845  	ss := convertSpamStatistics(stats)
   846  	if err != nil || ss.ChainID == "" {
   847  		s.policy.Report(SentTransaction{
   848  			TxID:  txID,
   849  			Error: ErrCouldNotGetChainID,
   850  		})
   851  		s.writeInternalError(w, ErrCouldNotGetChainID)
   852  		return
   853  	}
   854  	tx, err := s.handler.SignTx(name, req, ss.LastBlockHeight, ss.ChainID)
   855  	if err != nil {
   856  		s.policy.Report(SentTransaction{
   857  			TxID:  txID,
   858  			Error: err,
   859  		})
   860  		s.writeInternalError(w, err)
   861  		return
   862  	}
   863  
   864  	// generate proof of work for the transaction
   865  	tx.Pow, err = s.spam.GenerateProofOfWork(req.PubKey, ss)
   866  	if err != nil {
   867  		s.policy.Report(SentTransaction{
   868  			Tx:    tx,
   869  			TxID:  txID,
   870  			Error: err,
   871  		})
   872  		s.writeInternalError(w, err)
   873  		return
   874  	}
   875  	sentAt := time.Now()
   876  	resp, err := s.nodeForward.SendTx(r.Context(), tx, ty, cltIdx)
   877  	if err != nil {
   878  		s.policy.Report(SentTransaction{
   879  			Tx:     tx,
   880  			TxID:   txID,
   881  			Error:  err,
   882  			SentAt: sentAt,
   883  		})
   884  		s.writeInternalError(w, err)
   885  		return
   886  	}
   887  	if !resp.Success {
   888  		s.policy.Report(SentTransaction{
   889  			Tx:     tx,
   890  			TxID:   txID,
   891  			Error:  errors.New(resp.Data),
   892  			SentAt: sentAt,
   893  		})
   894  		s.writeTxError(w, resp)
   895  		return
   896  	}
   897  
   898  	s.policy.Report(SentTransaction{
   899  		TxHash: resp.TxHash,
   900  		TxID:   txID,
   901  		Tx:     tx,
   902  		SentAt: sentAt,
   903  	})
   904  
   905  	s.writeSuccess(w, struct {
   906  		TxHash     string                  `json:"txHash"`
   907  		ReceivedAt time.Time               `json:"receivedAt"`
   908  		SentAt     time.Time               `json:"sentAt"`
   909  		TxID       string                  `json:"txId"`
   910  		Tx         *commandspb.Transaction `json:"tx"`
   911  	}{
   912  		TxHash:     resp.TxHash,
   913  		ReceivedAt: receivedAt,
   914  		SentAt:     sentAt,
   915  		TxID:       txID,
   916  		Tx:         tx,
   917  	})
   918  }
   919  
   920  func (s *API) Version(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
   921  	res := VersionResponse{
   922  		Version:     version.Get(),
   923  		VersionHash: version.GetCommitHash(),
   924  	}
   925  
   926  	s.writeSuccess(w, res)
   927  }
   928  
   929  func (s *API) GetNetwork(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
   930  	res := NetworkResponse{
   931  		Network: *s.network,
   932  	}
   933  	s.writeSuccess(w, res)
   934  }
   935  
   936  func (s *API) Health(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
   937  	if err := s.nodeForward.HealthCheck(r.Context()); err != nil {
   938  		s.writeError(w, newErrorResponse(err.Error()), http.StatusFailedDependency)
   939  		return
   940  	}
   941  	s.writeSuccess(w, nil)
   942  }
   943  
   944  func (s *API) GetNetworkChainID(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
   945  	lastBlock, _, err := s.nodeForward.LastBlockHeightAndHash(r.Context())
   946  	if err != nil {
   947  		s.writeError(w, newErrorResponse(err.Error()), http.StatusFailedDependency)
   948  		return
   949  	}
   950  	s.writeSuccess(w, struct {
   951  		ChainID string `json:"chainID"`
   952  	}{
   953  		ChainID: lastBlock.ChainId,
   954  	})
   955  }
   956  
   957  func (s *API) writeBadRequestErr(w http.ResponseWriter, err error) {
   958  	errs := commands.NewErrors()
   959  	s.writeErrors(w, http.StatusBadRequest, errs.FinalAdd(err))
   960  }
   961  
   962  func (s *API) writeBadRequest(w http.ResponseWriter, errs commands.Errors) {
   963  	s.writeErrors(w, http.StatusBadRequest, errs)
   964  }
   965  
   966  func (s *API) writeErrors(w http.ResponseWriter, statusCode int, errs commands.Errors) {
   967  	w.Header().Set("Content-Type", "application/json")
   968  	w.WriteHeader(statusCode)
   969  	buf, err := json.Marshal(ErrorsResponse{Errors: errs})
   970  	if err != nil {
   971  		s.log.Error("couldn't marshal errors", zap.String("error", vfmt.Escape(errs.Error())))
   972  		w.WriteHeader(http.StatusInternalServerError)
   973  		return
   974  	}
   975  	if _, err := w.Write(buf); err != nil {
   976  		s.log.Error("couldn't write errors", zap.String("error", vfmt.Escape(errs.Error())))
   977  		w.WriteHeader(http.StatusInternalServerError)
   978  		return
   979  	}
   980  	s.log.Info(fmt.Sprintf("%d %s", statusCode, http.StatusText(statusCode)))
   981  }
   982  
   983  func unmarshalBody(r *http.Request, into interface{}) error {
   984  	defer r.Body.Close()
   985  	body, err := io.ReadAll(r.Body)
   986  	if err != nil {
   987  		return ErrCouldNotReadRequest
   988  	}
   989  	if len(body) == 0 {
   990  		return nil
   991  	}
   992  	return json.Unmarshal(body, into)
   993  }
   994  
   995  func (s *API) writeForbiddenError(w http.ResponseWriter, e error) {
   996  	s.writeError(w, newErrorResponse(e.Error()), http.StatusForbidden)
   997  }
   998  
   999  func (s *API) writeInternalError(w http.ResponseWriter, e error) {
  1000  	s.writeError(w, newErrorResponse(e.Error()), http.StatusInternalServerError)
  1001  }
  1002  
  1003  func (s *API) writeTxError(w http.ResponseWriter, r *api.SubmitTransactionResponse) {
  1004  	var code int
  1005  	switch r.Code {
  1006  	case TxnSpamError:
  1007  		code = http.StatusTooManyRequests
  1008  	case TxnUnknownCommandError, TxnValidationFailure, TxnDecodingFailure:
  1009  		code = http.StatusBadRequest
  1010  	case TxnInternalError:
  1011  		code = http.StatusInternalServerError
  1012  	default:
  1013  		s.log.Error("unknown transaction code", zap.Uint32("code", r.Code))
  1014  		code = http.StatusInternalServerError
  1015  	}
  1016  	s.writeError(w, newErrorResponse(r.Data), code)
  1017  }
  1018  
  1019  func (s *API) writeError(w http.ResponseWriter, e error, status int) {
  1020  	w.Header().Set("Content-Type", "application/json")
  1021  	w.WriteHeader(status)
  1022  
  1023  	buf, err := json.Marshal(e)
  1024  	if err != nil {
  1025  		s.log.Error("couldn't marshal error", zap.Error(err))
  1026  		w.WriteHeader(http.StatusInternalServerError)
  1027  		return
  1028  	}
  1029  	_, err = w.Write(buf)
  1030  	if err != nil {
  1031  		s.log.Error("couldn't write error to HTTP response", zap.Error(err))
  1032  		w.WriteHeader(http.StatusInternalServerError)
  1033  		return
  1034  	}
  1035  	s.log.Info(fmt.Sprintf("%d %s", status, http.StatusText(status)))
  1036  }
  1037  
  1038  func (s *API) writeSuccess(w http.ResponseWriter, data interface{}) {
  1039  	w.Header().Set("Content-Type", "application/json")
  1040  	w.WriteHeader(http.StatusOK)
  1041  
  1042  	if data == nil {
  1043  		s.log.Info(fmt.Sprintf("%d %s", http.StatusOK, http.StatusText(http.StatusOK)))
  1044  		return
  1045  	}
  1046  
  1047  	buf, err := json.Marshal(data)
  1048  	if err != nil {
  1049  		s.log.Error("couldn't marshal error", zap.Error(err))
  1050  		s.writeInternalError(w, fmt.Errorf("couldn't marshal error: %w", err))
  1051  		return
  1052  	}
  1053  
  1054  	_, err = w.Write(buf)
  1055  	if err != nil {
  1056  		s.log.Error("couldn't write error to HTTP response", zap.Error(err))
  1057  		s.writeInternalError(w, fmt.Errorf("couldn't write error to HTTP response: %w", err))
  1058  		return
  1059  	}
  1060  	s.log.Info(fmt.Sprintf("%d %s", http.StatusOK, http.StatusText(http.StatusOK)))
  1061  }
  1062  
  1063  // convertSpamStatistics takes us from the protos to the nodetypes version
  1064  // the code is copied from the V2 API but is not worth the pain of trying to share
  1065  // because V1 is disappearing soon anyway.
  1066  func convertSpamStatistics(r *api.GetSpamStatisticsResponse) *nodetypes.SpamStatistics {
  1067  	proposals := map[string]uint64{}
  1068  	for _, st := range r.Statistics.Votes.Statistics {
  1069  		proposals[st.Proposal] = st.CountForEpoch
  1070  	}
  1071  
  1072  	blockStates := []nodetypes.PoWBlockState{}
  1073  	for _, b := range r.Statistics.Pow.BlockStates {
  1074  		blockStates = append(blockStates, nodetypes.PoWBlockState{
  1075  			BlockHeight:          b.BlockHeight,
  1076  			BlockHash:            b.BlockHash,
  1077  			TransactionsSeen:     b.TransactionsSeen,
  1078  			ExpectedDifficulty:   b.ExpectedDifficulty,
  1079  			HashFunction:         b.HashFunction,
  1080  			TxPerBlock:           b.TxPerBlock,
  1081  			IncreasingDifficulty: b.IncreasingDifficulty,
  1082  			Difficulty:           b.Difficulty,
  1083  		})
  1084  	}
  1085  
  1086  	// sort by block-height so latest block is first
  1087  	sort.Slice(blockStates, func(i int, j int) bool {
  1088  		return blockStates[i].BlockHeight > blockStates[j].BlockHeight
  1089  	})
  1090  
  1091  	var lastBlockHeight uint64
  1092  	if len(blockStates) > 0 {
  1093  		lastBlockHeight = blockStates[0].BlockHeight
  1094  	}
  1095  	return &nodetypes.SpamStatistics{
  1096  		Proposals:         toSpamStatistic(r.Statistics.Proposals),
  1097  		Delegations:       toSpamStatistic(r.Statistics.Delegations),
  1098  		Transfers:         toSpamStatistic(r.Statistics.Transfers),
  1099  		NodeAnnouncements: toSpamStatistic(r.Statistics.NodeAnnouncements),
  1100  		IssuesSignatures:  toSpamStatistic(r.Statistics.IssueSignatures),
  1101  		CreateReferralSet: toSpamStatistic(r.Statistics.CreateReferralSet),
  1102  		UpdateReferralSet: toSpamStatistic(r.Statistics.UpdateReferralSet),
  1103  		ApplyReferralCode: toSpamStatistic(r.Statistics.ApplyReferralCode),
  1104  		Votes: &nodetypes.VoteSpamStatistics{
  1105  			Proposals:   proposals,
  1106  			MaxForEpoch: r.Statistics.Votes.MaxForEpoch,
  1107  			BannedUntil: r.Statistics.Votes.BannedUntil,
  1108  		},
  1109  		PoW: &nodetypes.PoWStatistics{
  1110  			PowBlockStates: blockStates,
  1111  			BannedUntil:    r.Statistics.Pow.BannedUntil,
  1112  			PastBlocks:     r.Statistics.Pow.NumberOfPastBlocks,
  1113  		},
  1114  		ChainID:         r.ChainId,
  1115  		EpochSeq:        r.Statistics.EpochSeq,
  1116  		LastBlockHeight: lastBlockHeight,
  1117  	}
  1118  }
  1119  
  1120  func toSpamStatistic(st *api.SpamStatistic) *nodetypes.SpamStatistic {
  1121  	return &nodetypes.SpamStatistic{
  1122  		CountForEpoch: st.CountForEpoch,
  1123  		MaxForEpoch:   st.MaxForEpoch,
  1124  		BannedUntil:   st.BannedUntil,
  1125  	}
  1126  }