github.com/ethersphere/bee/v2@v2.2.0/pkg/api/accesscontrol.go (about)

     1  // Copyright 2024 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package api
     6  
     7  import (
     8  	"context"
     9  	"crypto/ecdsa"
    10  	"encoding/hex"
    11  	"encoding/json"
    12  	"errors"
    13  	"fmt"
    14  	"io"
    15  	"net/http"
    16  	"time"
    17  
    18  	"github.com/btcsuite/btcd/btcec/v2"
    19  	"github.com/ethersphere/bee/v2/pkg/accesscontrol"
    20  	"github.com/ethersphere/bee/v2/pkg/crypto"
    21  	"github.com/ethersphere/bee/v2/pkg/file/loadsave"
    22  	"github.com/ethersphere/bee/v2/pkg/file/redundancy"
    23  	"github.com/ethersphere/bee/v2/pkg/jsonhttp"
    24  	"github.com/ethersphere/bee/v2/pkg/postage"
    25  	"github.com/ethersphere/bee/v2/pkg/storage"
    26  	"github.com/ethersphere/bee/v2/pkg/storer"
    27  	"github.com/ethersphere/bee/v2/pkg/swarm"
    28  	"github.com/gorilla/mux"
    29  )
    30  
    31  type addressKey struct{}
    32  
    33  const granteeListEncrypt = true
    34  
    35  // getAddressFromContext is a helper function to extract the address from the context.
    36  func getAddressFromContext(ctx context.Context) swarm.Address {
    37  	v, ok := ctx.Value(addressKey{}).(swarm.Address)
    38  	if ok {
    39  		return v
    40  	}
    41  	return swarm.ZeroAddress
    42  }
    43  
    44  // setAddressInContext sets the swarm address in the context.
    45  func setAddressInContext(ctx context.Context, address swarm.Address) context.Context {
    46  	return context.WithValue(ctx, addressKey{}, address)
    47  }
    48  
    49  // GranteesPatchRequest represents a request to patch the list of grantees.
    50  type GranteesPatchRequest struct {
    51  	// Addlist contains the list of grantees to add.
    52  	Addlist []string `json:"add"`
    53  
    54  	// Revokelist contains the list of grantees to revoke.
    55  	Revokelist []string `json:"revoke"`
    56  }
    57  
    58  // GranteesPatchResponse represents the response structure for patching grantees.
    59  type GranteesPatchResponse struct {
    60  	// Reference represents the swarm address.
    61  	Reference swarm.Address `json:"ref"`
    62  	// HistoryReference represents the reference to the history of an access control entry.
    63  	HistoryReference swarm.Address `json:"historyref"`
    64  }
    65  
    66  // GranteesPostRequest represents the request structure for adding grantees.
    67  type GranteesPostRequest struct {
    68  	// GranteeList represents the list of grantees to be saves on Swarm.
    69  	GranteeList []string `json:"grantees"`
    70  }
    71  
    72  // GranteesPostResponse represents the response structure for adding grantees.
    73  type GranteesPostResponse struct {
    74  	// Reference represents the saved grantee list Swarm address.
    75  	Reference swarm.Address `json:"ref"`
    76  	// HistoryReference represents the reference to the history of an access control entry.
    77  	HistoryReference swarm.Address `json:"historyref"`
    78  }
    79  
    80  // GranteesPatch represents a structure for modifying the list of grantees.
    81  type GranteesPatch struct {
    82  	// Addlist is a list of ecdsa.PublicKeys to be added to a grantee list.
    83  	Addlist []*ecdsa.PublicKey
    84  	// Revokelist is a list of ecdsa.PublicKeys to be removed from a grantee list
    85  	Revokelist []*ecdsa.PublicKey
    86  }
    87  
    88  // actDecryptionHandler is a middleware that looks up and decrypts the given address,
    89  // if the act headers are present.
    90  func (s *Service) actDecryptionHandler() func(h http.Handler) http.Handler {
    91  	return func(h http.Handler) http.Handler {
    92  		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    93  			logger := s.logger.WithName("act_decryption_handler").Build()
    94  			paths := struct {
    95  				Address swarm.Address `map:"address,resolve" validate:"required"`
    96  			}{}
    97  			if response := s.mapStructure(mux.Vars(r), &paths); response != nil {
    98  				response("invalid path params", logger, w)
    99  				return
   100  			}
   101  
   102  			headers := struct {
   103  				Timestamp      *int64           `map:"Swarm-Act-Timestamp"`
   104  				Publisher      *ecdsa.PublicKey `map:"Swarm-Act-Publisher"`
   105  				HistoryAddress *swarm.Address   `map:"Swarm-Act-History-Address"`
   106  				Cache          *bool            `map:"Swarm-Cache"`
   107  			}{}
   108  			if response := s.mapStructure(r.Header, &headers); response != nil {
   109  				response("invalid header params", logger, w)
   110  				return
   111  			}
   112  
   113  			// Try to download the file wihthout decryption, if the act headers are not present
   114  			if headers.Publisher == nil || headers.HistoryAddress == nil {
   115  				h.ServeHTTP(w, r)
   116  				return
   117  			}
   118  
   119  			timestamp := time.Now().Unix()
   120  			if headers.Timestamp != nil {
   121  				timestamp = *headers.Timestamp
   122  			}
   123  
   124  			cache := true
   125  			if headers.Cache != nil {
   126  				cache = *headers.Cache
   127  			}
   128  			ctx := r.Context()
   129  			ls := loadsave.NewReadonly(s.storer.Download(cache))
   130  			reference, err := s.accesscontrol.DownloadHandler(ctx, ls, paths.Address, headers.Publisher, *headers.HistoryAddress, timestamp)
   131  			if err != nil {
   132  				logger.Debug("access control download failed", "error", err)
   133  				logger.Error(nil, "access control download failed")
   134  				switch {
   135  				case errors.Is(err, accesscontrol.ErrNotFound):
   136  					jsonhttp.NotFound(w, "act or history entry not found")
   137  				case errors.Is(err, accesscontrol.ErrInvalidTimestamp):
   138  					jsonhttp.BadRequest(w, "invalid timestamp")
   139  				case errors.Is(err, accesscontrol.ErrInvalidPublicKey) || errors.Is(err, accesscontrol.ErrSecretKeyInfinity):
   140  					jsonhttp.BadRequest(w, "invalid public key")
   141  				case errors.Is(err, accesscontrol.ErrUnexpectedType):
   142  					jsonhttp.BadRequest(w, "failed to create history")
   143  				default:
   144  					jsonhttp.InternalServerError(w, errActDownload)
   145  				}
   146  				return
   147  			}
   148  			h.ServeHTTP(w, r.WithContext(setAddressInContext(ctx, reference)))
   149  		})
   150  	}
   151  }
   152  
   153  // actEncryptionHandler is a middleware that encrypts the given address using the publisher's public key,
   154  // uploads the encrypted reference, history and kvs to the store.
   155  func (s *Service) actEncryptionHandler(
   156  	ctx context.Context,
   157  	w http.ResponseWriter,
   158  	putter storer.PutterSession,
   159  	reference swarm.Address,
   160  	historyRootHash swarm.Address,
   161  ) (swarm.Address, error) {
   162  	publisherPublicKey := &s.publicKey
   163  	ls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE))
   164  	storageReference, historyReference, encryptedReference, err := s.accesscontrol.UploadHandler(ctx, ls, reference, publisherPublicKey, historyRootHash)
   165  	if err != nil {
   166  		return swarm.ZeroAddress, err
   167  	}
   168  	// only need to upload history and kvs if a new history is created,
   169  	// meaning that the publisher uploaded to the history for the first time
   170  	if !historyReference.Equal(historyRootHash) {
   171  		err = putter.Done(storageReference)
   172  		if err != nil {
   173  			return swarm.ZeroAddress, fmt.Errorf("done split key-value store failed: %w", err)
   174  		}
   175  		err = putter.Done(historyReference)
   176  		if err != nil {
   177  			return swarm.ZeroAddress, fmt.Errorf("done split history failed: %w", err)
   178  		}
   179  	}
   180  
   181  	w.Header().Set(SwarmActHistoryAddressHeader, historyReference.String())
   182  	return encryptedReference, nil
   183  }
   184  
   185  // actListGranteesHandler is a middleware that decrypts the given address and returns the list of grantees,
   186  // only the publisher is authorized to access the list.
   187  func (s *Service) actListGranteesHandler(w http.ResponseWriter, r *http.Request) {
   188  	logger := s.logger.WithName("act_list_grantees_handler").Build()
   189  	paths := struct {
   190  		GranteesAddress swarm.Address `map:"address,resolve" validate:"required"`
   191  	}{}
   192  	if response := s.mapStructure(mux.Vars(r), &paths); response != nil {
   193  		response("invalid path params", logger, w)
   194  		return
   195  	}
   196  
   197  	headers := struct {
   198  		Cache *bool `map:"Swarm-Cache"`
   199  	}{}
   200  	if response := s.mapStructure(r.Header, &headers); response != nil {
   201  		response("invalid header params", logger, w)
   202  		return
   203  	}
   204  	cache := true
   205  	if headers.Cache != nil {
   206  		cache = *headers.Cache
   207  	}
   208  	publisher := &s.publicKey
   209  	ls := loadsave.NewReadonly(s.storer.Download(cache))
   210  	grantees, err := s.accesscontrol.Get(r.Context(), ls, publisher, paths.GranteesAddress)
   211  	if err != nil {
   212  		logger.Debug("could not get grantees", "error", err)
   213  		logger.Error(nil, "could not get grantees")
   214  		jsonhttp.NotFound(w, "granteelist not found")
   215  		return
   216  	}
   217  	granteeSlice := make([]string, len(grantees))
   218  	for i, grantee := range grantees {
   219  		granteeSlice[i] = hex.EncodeToString(crypto.EncodeSecp256k1PublicKey(grantee))
   220  	}
   221  	jsonhttp.OK(w, granteeSlice)
   222  }
   223  
   224  // actGrantRevokeHandler is a middleware that makes updates to the list of grantees,
   225  // only the publisher is authorized to perform this action.
   226  func (s *Service) actGrantRevokeHandler(w http.ResponseWriter, r *http.Request) {
   227  	logger := s.logger.WithName("act_grant_revoke_handler").Build()
   228  
   229  	if r.Body == http.NoBody {
   230  		logger.Error(nil, "request has no body")
   231  		jsonhttp.BadRequest(w, errInvalidRequest)
   232  		return
   233  	}
   234  
   235  	paths := struct {
   236  		GranteesAddress swarm.Address `map:"address,resolve" validate:"required"`
   237  	}{}
   238  	if response := s.mapStructure(mux.Vars(r), &paths); response != nil {
   239  		response("invalid path params", logger, w)
   240  		return
   241  	}
   242  
   243  	headers := struct {
   244  		BatchID        []byte         `map:"Swarm-Postage-Batch-Id" validate:"required"`
   245  		SwarmTag       uint64         `map:"Swarm-Tag"`
   246  		Pin            bool           `map:"Swarm-Pin"`
   247  		Deferred       *bool          `map:"Swarm-Deferred-Upload"`
   248  		HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address" validate:"required"`
   249  	}{}
   250  	if response := s.mapStructure(r.Header, &headers); response != nil {
   251  		response("invalid header params", logger, w)
   252  		return
   253  	}
   254  
   255  	historyAddress := swarm.ZeroAddress
   256  	if headers.HistoryAddress != nil {
   257  		historyAddress = *headers.HistoryAddress
   258  	}
   259  
   260  	var (
   261  		tag      uint64
   262  		err      error
   263  		deferred = defaultUploadMethod(headers.Deferred)
   264  	)
   265  
   266  	if deferred || headers.Pin {
   267  		tag, err = s.getOrCreateSessionID(headers.SwarmTag)
   268  		if err != nil {
   269  			logger.Debug("get or create tag failed", "error", err)
   270  			logger.Error(nil, "get or create tag failed")
   271  			switch {
   272  			case errors.Is(err, storage.ErrNotFound):
   273  				jsonhttp.NotFound(w, "tag not found")
   274  			default:
   275  				jsonhttp.InternalServerError(w, "cannot get or create tag")
   276  			}
   277  			return
   278  		}
   279  	}
   280  
   281  	body, err := io.ReadAll(r.Body)
   282  	if err != nil {
   283  		if jsonhttp.HandleBodyReadError(err, w) {
   284  			return
   285  		}
   286  		logger.Debug("read request body failed", "error", err)
   287  		logger.Error(nil, "read request body failed")
   288  		jsonhttp.InternalServerError(w, "cannot read request")
   289  		return
   290  	}
   291  
   292  	gpr := GranteesPatchRequest{}
   293  	if len(body) > 0 {
   294  		err = json.Unmarshal(body, &gpr)
   295  		if err != nil {
   296  			logger.Debug("unmarshal body failed", "error", err)
   297  			logger.Error(nil, "unmarshal body failed")
   298  			jsonhttp.InternalServerError(w, "error unmarshaling request body")
   299  			return
   300  		}
   301  	}
   302  
   303  	grantees := GranteesPatch{}
   304  	parsedAddlist, err := parseKeys(gpr.Addlist)
   305  	if err != nil {
   306  		logger.Debug("add list key parse failed", "error", err)
   307  		logger.Error(nil, "add list key parse failed")
   308  		jsonhttp.BadRequest(w, "invalid add list")
   309  		return
   310  	}
   311  	grantees.Addlist = append(grantees.Addlist, parsedAddlist...)
   312  
   313  	parsedRevokelist, err := parseKeys(gpr.Revokelist)
   314  	if err != nil {
   315  		logger.Debug("revoke list key parse failed", "error", err)
   316  		logger.Error(nil, "revoke list key parse failed")
   317  		jsonhttp.BadRequest(w, "invalid revoke list")
   318  		return
   319  	}
   320  	grantees.Revokelist = append(grantees.Revokelist, parsedRevokelist...)
   321  
   322  	ctx := r.Context()
   323  	putter, err := s.newStamperPutter(ctx, putterOptions{
   324  		BatchID:  headers.BatchID,
   325  		TagID:    tag,
   326  		Pin:      headers.Pin,
   327  		Deferred: deferred,
   328  	})
   329  	if err != nil {
   330  		logger.Debug("putter failed", "error", err)
   331  		logger.Error(nil, "putter failed")
   332  		switch {
   333  		case errors.Is(err, errBatchUnusable) || errors.Is(err, postage.ErrNotUsable):
   334  			jsonhttp.UnprocessableEntity(w, "batch not usable yet or does not exist")
   335  		case errors.Is(err, postage.ErrNotFound):
   336  			jsonhttp.NotFound(w, "batch with id not found")
   337  		case errors.Is(err, errInvalidPostageBatch):
   338  			jsonhttp.BadRequest(w, "invalid batch id")
   339  		case errors.Is(err, errUnsupportedDevNodeOperation):
   340  			jsonhttp.BadRequest(w, errUnsupportedDevNodeOperation)
   341  		default:
   342  			jsonhttp.BadRequest(w, nil)
   343  		}
   344  		return
   345  	}
   346  
   347  	granteeref := paths.GranteesAddress
   348  	publisher := &s.publicKey
   349  	ls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE))
   350  	gls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, granteeListEncrypt, redundancy.NONE))
   351  	granteeref, encryptedglref, historyref, actref, err := s.accesscontrol.UpdateHandler(ctx, ls, gls, granteeref, historyAddress, publisher, grantees.Addlist, grantees.Revokelist)
   352  	if err != nil {
   353  		logger.Debug("failed to update grantee list", "error", err)
   354  		logger.Error(nil, "failed to update grantee list")
   355  		switch {
   356  		case errors.Is(err, accesscontrol.ErrNotFound):
   357  			jsonhttp.NotFound(w, "act or history entry not found")
   358  		case errors.Is(err, accesscontrol.ErrNoGranteeFound):
   359  			jsonhttp.BadRequest(w, "remove from empty grantee list")
   360  		case errors.Is(err, accesscontrol.ErrUnexpectedType):
   361  			jsonhttp.BadRequest(w, "failed to create history")
   362  		default:
   363  			jsonhttp.InternalServerError(w, errActGranteeList)
   364  		}
   365  		return
   366  	}
   367  
   368  	err = putter.Done(actref)
   369  	if err != nil {
   370  		logger.Debug("done split act failed", "error", err)
   371  		logger.Error(nil, "done split act failed")
   372  		jsonhttp.InternalServerError(w, "done split act failed")
   373  		return
   374  	}
   375  
   376  	err = putter.Done(historyref)
   377  	if err != nil {
   378  		logger.Debug("done split history failed", "error", err)
   379  		logger.Error(nil, "done split history failed")
   380  		jsonhttp.InternalServerError(w, "done split history failed")
   381  		return
   382  	}
   383  
   384  	err = putter.Done(granteeref)
   385  	if err != nil {
   386  		logger.Debug("done split grantees failed", "error", err)
   387  		logger.Error(nil, "done split grantees failed")
   388  		jsonhttp.InternalServerError(w, "done split grantees failed")
   389  		return
   390  	}
   391  
   392  	jsonhttp.OK(w, GranteesPatchResponse{
   393  		Reference:        encryptedglref,
   394  		HistoryReference: historyref,
   395  	})
   396  }
   397  
   398  // actCreateGranteesHandler is a middleware that creates a new list of grantees,
   399  // only the publisher is authorized to perform this action.
   400  func (s *Service) actCreateGranteesHandler(w http.ResponseWriter, r *http.Request) {
   401  	logger := s.logger.WithName("acthandler").Build()
   402  
   403  	if r.Body == http.NoBody {
   404  		logger.Error(nil, "request has no body")
   405  		jsonhttp.BadRequest(w, errInvalidRequest)
   406  		return
   407  	}
   408  
   409  	headers := struct {
   410  		BatchID        []byte         `map:"Swarm-Postage-Batch-Id" validate:"required"`
   411  		SwarmTag       uint64         `map:"Swarm-Tag"`
   412  		Pin            bool           `map:"Swarm-Pin"`
   413  		Deferred       *bool          `map:"Swarm-Deferred-Upload"`
   414  		HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address"`
   415  	}{}
   416  	if response := s.mapStructure(r.Header, &headers); response != nil {
   417  		response("invalid header params", logger, w)
   418  		return
   419  	}
   420  
   421  	historyAddress := swarm.ZeroAddress
   422  	if headers.HistoryAddress != nil {
   423  		historyAddress = *headers.HistoryAddress
   424  	}
   425  
   426  	var (
   427  		tag      uint64
   428  		err      error
   429  		deferred = defaultUploadMethod(headers.Deferred)
   430  	)
   431  
   432  	if deferred || headers.Pin {
   433  		tag, err = s.getOrCreateSessionID(headers.SwarmTag)
   434  		if err != nil {
   435  			logger.Debug("get or create tag failed", "error", err)
   436  			logger.Error(nil, "get or create tag failed")
   437  			switch {
   438  			case errors.Is(err, storage.ErrNotFound):
   439  				jsonhttp.NotFound(w, "tag not found")
   440  			default:
   441  				jsonhttp.InternalServerError(w, "cannot get or create tag")
   442  			}
   443  			return
   444  		}
   445  	}
   446  
   447  	body, err := io.ReadAll(r.Body)
   448  	if err != nil {
   449  		if jsonhttp.HandleBodyReadError(err, w) {
   450  			return
   451  		}
   452  		logger.Debug("read request body failed", "error", err)
   453  		logger.Error(nil, "read request body failed")
   454  		jsonhttp.InternalServerError(w, "cannot read request")
   455  		return
   456  	}
   457  
   458  	gpr := GranteesPostRequest{}
   459  	if len(body) > 0 {
   460  		err = json.Unmarshal(body, &gpr)
   461  		if err != nil {
   462  			logger.Debug("unmarshal body failed", "error", err)
   463  			logger.Error(nil, "unmarshal body failed")
   464  			jsonhttp.InternalServerError(w, "error unmarshaling request body")
   465  			return
   466  		}
   467  	}
   468  
   469  	list, err := parseKeys(gpr.GranteeList)
   470  	if err != nil {
   471  		logger.Debug("create list key parse failed", "error", err)
   472  		logger.Error(nil, "create list key parse failed")
   473  		jsonhttp.BadRequest(w, "invalid grantee list")
   474  		return
   475  	}
   476  
   477  	ctx := r.Context()
   478  	putter, err := s.newStamperPutter(ctx, putterOptions{
   479  		BatchID:  headers.BatchID,
   480  		TagID:    tag,
   481  		Pin:      headers.Pin,
   482  		Deferred: deferred,
   483  	})
   484  	if err != nil {
   485  		logger.Debug("putter failed", "error", err)
   486  		logger.Error(nil, "putter failed")
   487  		switch {
   488  		case errors.Is(err, errBatchUnusable) || errors.Is(err, postage.ErrNotUsable):
   489  			jsonhttp.UnprocessableEntity(w, "batch not usable yet or does not exist")
   490  		case errors.Is(err, postage.ErrNotFound):
   491  			jsonhttp.NotFound(w, "batch with id not found")
   492  		case errors.Is(err, errInvalidPostageBatch):
   493  			jsonhttp.BadRequest(w, "invalid batch id")
   494  		case errors.Is(err, errUnsupportedDevNodeOperation):
   495  			jsonhttp.BadRequest(w, errUnsupportedDevNodeOperation)
   496  		default:
   497  			jsonhttp.BadRequest(w, nil)
   498  		}
   499  		return
   500  	}
   501  
   502  	publisher := &s.publicKey
   503  	ls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE))
   504  	gls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, granteeListEncrypt, redundancy.NONE))
   505  	granteeref, encryptedglref, historyref, actref, err := s.accesscontrol.UpdateHandler(ctx, ls, gls, swarm.ZeroAddress, historyAddress, publisher, list, nil)
   506  	if err != nil {
   507  		logger.Debug("failed to create grantee list", "error", err)
   508  		logger.Error(nil, "failed to create grantee list")
   509  		switch {
   510  		case errors.Is(err, accesscontrol.ErrNotFound):
   511  			jsonhttp.NotFound(w, "act or history entry not found")
   512  		case errors.Is(err, accesscontrol.ErrUnexpectedType):
   513  			jsonhttp.BadRequest(w, "failed to create history")
   514  		default:
   515  			jsonhttp.InternalServerError(w, errActGranteeList)
   516  		}
   517  		return
   518  	}
   519  
   520  	err = putter.Done(actref)
   521  	if err != nil {
   522  		logger.Debug("done split act failed", "error", err)
   523  		logger.Error(nil, "done split act failed")
   524  		jsonhttp.InternalServerError(w, "done split act failed")
   525  		return
   526  	}
   527  
   528  	err = putter.Done(historyref)
   529  	if err != nil {
   530  		logger.Debug("done split history failed", "error", err)
   531  		logger.Error(nil, "done split history failed")
   532  		jsonhttp.InternalServerError(w, "done split history failed")
   533  		return
   534  	}
   535  
   536  	err = putter.Done(granteeref)
   537  	if err != nil {
   538  		logger.Debug("done split grantees failed", "error", err)
   539  		logger.Error(nil, "done split grantees failed")
   540  		jsonhttp.InternalServerError(w, "done split grantees failed")
   541  		return
   542  	}
   543  
   544  	jsonhttp.Created(w, GranteesPostResponse{
   545  		Reference:        encryptedglref,
   546  		HistoryReference: historyref,
   547  	})
   548  }
   549  
   550  func parseKeys(list []string) ([]*ecdsa.PublicKey, error) {
   551  	parsedList := make([]*ecdsa.PublicKey, 0, len(list))
   552  	for _, g := range list {
   553  		h, err := hex.DecodeString(g)
   554  		if err != nil {
   555  			return []*ecdsa.PublicKey{}, fmt.Errorf("failed to decode grantee: %w", err)
   556  		}
   557  		k, err := btcec.ParsePubKey(h)
   558  		if err != nil {
   559  			return []*ecdsa.PublicKey{}, fmt.Errorf("failed to parse grantee public key: %w", err)
   560  		}
   561  		parsedList = append(parsedList, k.ToECDSA())
   562  	}
   563  
   564  	return parsedList, nil
   565  }