github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/fvm/environment/account_key_updater.go (about)

     1  package environment
     2  
     3  import (
     4  	"encoding/hex"
     5  	"fmt"
     6  
     7  	"github.com/onflow/cadence/runtime"
     8  	"github.com/onflow/cadence/runtime/common"
     9  	"github.com/onflow/cadence/runtime/sema"
    10  
    11  	fgcrypto "github.com/onflow/crypto"
    12  	fghash "github.com/onflow/crypto/hash"
    13  
    14  	"github.com/onflow/flow-go/fvm/crypto"
    15  	"github.com/onflow/flow-go/fvm/errors"
    16  	"github.com/onflow/flow-go/fvm/storage/state"
    17  	"github.com/onflow/flow-go/fvm/tracing"
    18  	"github.com/onflow/flow-go/model/flow"
    19  	"github.com/onflow/flow-go/module/trace"
    20  )
    21  
    22  // NewAccountPublicKey construct an account public key given a runtime
    23  // public key.
    24  func NewAccountPublicKey(publicKey *runtime.PublicKey,
    25  	hashAlgo sema.HashAlgorithm,
    26  	keyIndex int,
    27  	weight int,
    28  ) (
    29  	*flow.AccountPublicKey,
    30  	error,
    31  ) {
    32  
    33  	var err error
    34  	signAlgorithm := crypto.RuntimeToCryptoSigningAlgorithm(publicKey.SignAlgo)
    35  	if signAlgorithm != fgcrypto.ECDSAP256 &&
    36  		signAlgorithm != fgcrypto.ECDSASecp256k1 {
    37  
    38  		return nil, fmt.Errorf(
    39  			"adding account key failed: %w",
    40  			errors.NewValueErrorf(
    41  				fmt.Sprintf("%d", publicKey.SignAlgo),
    42  				"signature algorithm type not supported"))
    43  	}
    44  
    45  	hashAlgorithm := crypto.RuntimeToCryptoHashingAlgorithm(hashAlgo)
    46  	if hashAlgorithm != fghash.SHA2_256 &&
    47  		hashAlgorithm != fghash.SHA3_256 {
    48  
    49  		return nil, fmt.Errorf(
    50  			"adding account key failed: %w",
    51  			errors.NewValueErrorf(
    52  				fmt.Sprintf("%d", hashAlgo),
    53  				"hashing algorithm type not supported"))
    54  	}
    55  
    56  	decodedPublicKey, err := fgcrypto.DecodePublicKey(
    57  		signAlgorithm,
    58  		publicKey.PublicKey)
    59  	if err != nil {
    60  		return nil, fmt.Errorf(
    61  			"adding account key failed: %w",
    62  			errors.NewValueErrorf(
    63  				hex.EncodeToString(publicKey.PublicKey),
    64  				"cannot decode public key: %w",
    65  				err))
    66  	}
    67  
    68  	return &flow.AccountPublicKey{
    69  		Index:     keyIndex,
    70  		PublicKey: decodedPublicKey,
    71  		SignAlgo:  signAlgorithm,
    72  		HashAlgo:  hashAlgorithm,
    73  		SeqNumber: 0,
    74  		Weight:    weight,
    75  		Revoked:   false,
    76  	}, nil
    77  }
    78  
    79  // AccountKeyUpdater handles all account keys modification.
    80  //
    81  // Note that scripts cannot modify account keys, but must expose the API in
    82  // compliance with the runtime environment interface.
    83  type AccountKeyUpdater interface {
    84  
    85  	// AddAccountKey adds a public key to an existing account.
    86  	//
    87  	// This function returns an error if the specified account does not exist or
    88  	// if the key insertion fails.
    89  	//
    90  	// Note that the script variant will return OperationNotSupportedError.
    91  	AddAccountKey(
    92  		runtimeAddress common.Address,
    93  		publicKey *runtime.PublicKey,
    94  		hashAlgo runtime.HashAlgorithm,
    95  		weight int,
    96  	) (
    97  		*runtime.AccountKey,
    98  		error,
    99  	)
   100  
   101  	// RevokeAccountKey revokes a public key by index from an existing account,
   102  	// and returns the revoked key.
   103  	//
   104  	// This function returns a nil key with no errors, if a key doesn't exist
   105  	// at the given index.  An error is returned if the specified account does
   106  	// not exist, the provided index is not valid, or if the key revoking
   107  	// fails.
   108  	//
   109  	// Note that the script variant will return OperationNotSupportedError.
   110  	RevokeAccountKey(
   111  		runtimeAddress common.Address,
   112  		keyIndex int,
   113  	) (
   114  		*runtime.AccountKey,
   115  		error,
   116  	)
   117  }
   118  
   119  type ParseRestrictedAccountKeyUpdater struct {
   120  	txnState state.NestedTransactionPreparer
   121  	impl     AccountKeyUpdater
   122  }
   123  
   124  func NewParseRestrictedAccountKeyUpdater(
   125  	txnState state.NestedTransactionPreparer,
   126  	impl AccountKeyUpdater,
   127  ) ParseRestrictedAccountKeyUpdater {
   128  	return ParseRestrictedAccountKeyUpdater{
   129  		txnState: txnState,
   130  		impl:     impl,
   131  	}
   132  }
   133  
   134  func (updater ParseRestrictedAccountKeyUpdater) AddAccountKey(
   135  	runtimeAddress common.Address,
   136  	publicKey *runtime.PublicKey,
   137  	hashAlgo runtime.HashAlgorithm,
   138  	weight int,
   139  ) (
   140  	*runtime.AccountKey,
   141  	error,
   142  ) {
   143  	return parseRestrict4Arg1Ret(
   144  		updater.txnState,
   145  		trace.FVMEnvAddAccountKey,
   146  		updater.impl.AddAccountKey,
   147  		runtimeAddress,
   148  		publicKey,
   149  		hashAlgo,
   150  		weight)
   151  }
   152  
   153  func (updater ParseRestrictedAccountKeyUpdater) RevokeAccountKey(
   154  	runtimeAddress common.Address,
   155  	keyIndex int,
   156  ) (
   157  	*runtime.AccountKey,
   158  	error,
   159  ) {
   160  	return parseRestrict2Arg1Ret(
   161  		updater.txnState,
   162  		trace.FVMEnvRevokeAccountKey,
   163  		updater.impl.RevokeAccountKey,
   164  		runtimeAddress,
   165  		keyIndex)
   166  }
   167  
   168  type NoAccountKeyUpdater struct{}
   169  
   170  func (NoAccountKeyUpdater) AddAccountKey(
   171  	runtimeAddress common.Address,
   172  	publicKey *runtime.PublicKey,
   173  	hashAlgo runtime.HashAlgorithm,
   174  	weight int,
   175  ) (
   176  	*runtime.AccountKey,
   177  	error,
   178  ) {
   179  	return nil, errors.NewOperationNotSupportedError("AddAccountKey")
   180  }
   181  
   182  func (NoAccountKeyUpdater) RevokeAccountKey(
   183  	runtimeAddress common.Address,
   184  	keyIndex int,
   185  ) (
   186  	*runtime.AccountKey,
   187  	error,
   188  ) {
   189  	return nil, errors.NewOperationNotSupportedError("RevokeAccountKey")
   190  }
   191  
   192  type accountKeyUpdater struct {
   193  	tracer tracing.TracerSpan
   194  	meter  Meter
   195  
   196  	accounts Accounts
   197  	txnState state.NestedTransactionPreparer
   198  	env      Environment
   199  }
   200  
   201  func NewAccountKeyUpdater(
   202  	tracer tracing.TracerSpan,
   203  	meter Meter,
   204  	accounts Accounts,
   205  	txnState state.NestedTransactionPreparer,
   206  	env Environment,
   207  ) *accountKeyUpdater {
   208  	return &accountKeyUpdater{
   209  		tracer:   tracer,
   210  		meter:    meter,
   211  		accounts: accounts,
   212  		txnState: txnState,
   213  		env:      env,
   214  	}
   215  }
   216  
   217  // AddAccountKey adds a public key to an existing account.
   218  //
   219  // This function returns an error if the specified account does not exist or
   220  // if the key insertion fails.
   221  func (updater *accountKeyUpdater) addAccountKey(
   222  	address flow.Address,
   223  	publicKey *runtime.PublicKey,
   224  	hashAlgo runtime.HashAlgorithm,
   225  	weight int,
   226  ) (
   227  	*runtime.AccountKey,
   228  	error,
   229  ) {
   230  	ok, err := updater.accounts.Exists(address)
   231  	if err != nil {
   232  		return nil, fmt.Errorf("adding account key failed: %w", err)
   233  	}
   234  	if !ok {
   235  		return nil, fmt.Errorf(
   236  			"adding account key failed: %w",
   237  			errors.NewAccountNotFoundError(address))
   238  	}
   239  
   240  	keyIndex, err := updater.accounts.GetPublicKeyCount(address)
   241  	if err != nil {
   242  		return nil, fmt.Errorf("adding account key failed: %w", err)
   243  	}
   244  
   245  	accountPublicKey, err := NewAccountPublicKey(
   246  		publicKey,
   247  		hashAlgo,
   248  		int(keyIndex),
   249  		weight)
   250  	if err != nil {
   251  		return nil, fmt.Errorf("adding account key failed: %w", err)
   252  	}
   253  
   254  	err = updater.accounts.AppendPublicKey(address, *accountPublicKey)
   255  	if err != nil {
   256  		return nil, fmt.Errorf("adding account key failed: %w", err)
   257  	}
   258  
   259  	return &runtime.AccountKey{
   260  		KeyIndex:  accountPublicKey.Index,
   261  		PublicKey: publicKey,
   262  		HashAlgo:  hashAlgo,
   263  		Weight:    accountPublicKey.Weight,
   264  		IsRevoked: accountPublicKey.Revoked,
   265  	}, nil
   266  }
   267  
   268  // RevokeAccountKey revokes a public key by index from an existing account,
   269  // and returns the revoked key.
   270  //
   271  // This function returns a nil key with no errors, if a key doesn't exist at
   272  // the given index. An error is returned if the specified account does not
   273  // exist, the provided index is not valid, or if the key revoking fails.
   274  //
   275  // TODO (ramtin) do we have to return runtime.AccountKey for this method or
   276  // can be separated into another method
   277  func (updater *accountKeyUpdater) revokeAccountKey(
   278  	address flow.Address,
   279  	keyIndex int,
   280  ) (
   281  	*runtime.AccountKey,
   282  	error,
   283  ) {
   284  	ok, err := updater.accounts.Exists(address)
   285  	if err != nil {
   286  		return nil, fmt.Errorf("revoking account key failed: %w", err)
   287  	}
   288  
   289  	if !ok {
   290  		return nil, fmt.Errorf(
   291  			"revoking account key failed: %w",
   292  			errors.NewAccountNotFoundError(address))
   293  	}
   294  
   295  	// Don't return an error for invalid key indices
   296  	if keyIndex < 0 {
   297  		return nil, nil
   298  	}
   299  
   300  	var publicKey flow.AccountPublicKey
   301  	publicKey, err = updater.accounts.GetPublicKey(
   302  		address,
   303  		uint64(keyIndex))
   304  	if err != nil {
   305  		// If a key is not found at a given index, then return a nil key with
   306  		// no errors.  This is to be inline with the Cadence runtime. Otherwise
   307  		// Cadence runtime cannot distinguish between a 'key not found error'
   308  		// vs other internal errors.
   309  		if errors.IsAccountPublicKeyNotFoundError(err) {
   310  			return nil, nil
   311  		}
   312  		return nil, fmt.Errorf("revoking account key failed: %w", err)
   313  	}
   314  
   315  	// mark this key as revoked
   316  	publicKey.Revoked = true
   317  
   318  	_, err = updater.accounts.SetPublicKey(
   319  		address,
   320  		uint64(keyIndex),
   321  		publicKey)
   322  	if err != nil {
   323  		return nil, fmt.Errorf("revoking account key failed: %w", err)
   324  	}
   325  
   326  	// Prepare account key to return
   327  	signAlgo := crypto.CryptoToRuntimeSigningAlgorithm(publicKey.SignAlgo)
   328  	if signAlgo == runtime.SignatureAlgorithmUnknown {
   329  		return nil, fmt.Errorf(
   330  			"revoking account key failed: %w",
   331  			errors.NewValueErrorf(
   332  				publicKey.SignAlgo.String(),
   333  				"signature algorithm type not found"))
   334  	}
   335  
   336  	hashAlgo := crypto.CryptoToRuntimeHashingAlgorithm(publicKey.HashAlgo)
   337  	if hashAlgo == runtime.HashAlgorithmUnknown {
   338  		return nil, fmt.Errorf(
   339  			"revoking account key failed: %w",
   340  			errors.NewValueErrorf(
   341  				publicKey.HashAlgo.String(),
   342  				"hashing algorithm type not found"))
   343  	}
   344  
   345  	return &runtime.AccountKey{
   346  		KeyIndex: publicKey.Index,
   347  		PublicKey: &runtime.PublicKey{
   348  			PublicKey: publicKey.PublicKey.Encode(),
   349  			SignAlgo:  signAlgo,
   350  		},
   351  		HashAlgo:  hashAlgo,
   352  		Weight:    publicKey.Weight,
   353  		IsRevoked: publicKey.Revoked,
   354  	}, nil
   355  }
   356  
   357  func (updater *accountKeyUpdater) AddAccountKey(
   358  	runtimeAddress common.Address,
   359  	publicKey *runtime.PublicKey,
   360  	hashAlgo runtime.HashAlgorithm,
   361  	weight int,
   362  ) (
   363  	*runtime.AccountKey,
   364  	error,
   365  ) {
   366  	defer updater.tracer.StartChildSpan(trace.FVMEnvAddAccountKey).End()
   367  
   368  	err := updater.meter.MeterComputation(
   369  		ComputationKindAddAccountKey,
   370  		1)
   371  	if err != nil {
   372  		return nil, fmt.Errorf("add account key failed: %w", err)
   373  	}
   374  
   375  	accKey, err := updater.addAccountKey(
   376  		flow.ConvertAddress(runtimeAddress),
   377  		publicKey,
   378  		hashAlgo,
   379  		weight)
   380  	if err != nil {
   381  		return nil, fmt.Errorf("add account key failed: %w", err)
   382  	}
   383  
   384  	return accKey, nil
   385  }
   386  
   387  func (updater *accountKeyUpdater) RevokeAccountKey(
   388  	runtimeAddress common.Address,
   389  	keyIndex int,
   390  ) (
   391  	*runtime.AccountKey,
   392  	error,
   393  ) {
   394  	defer updater.tracer.StartChildSpan(trace.FVMEnvRevokeAccountKey).End()
   395  
   396  	err := updater.meter.MeterComputation(
   397  		ComputationKindRevokeAccountKey,
   398  		1)
   399  	if err != nil {
   400  		return nil, fmt.Errorf("revoke account key failed: %w", err)
   401  	}
   402  
   403  	return updater.revokeAccountKey(
   404  		flow.ConvertAddress(runtimeAddress),
   405  		keyIndex)
   406  }