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

     1  package fvm
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  
     8  	"go.opentelemetry.io/otel/attribute"
     9  
    10  	"github.com/onflow/flow-go/fvm/crypto"
    11  	"github.com/onflow/flow-go/fvm/environment"
    12  	"github.com/onflow/flow-go/fvm/errors"
    13  	"github.com/onflow/flow-go/fvm/storage"
    14  	"github.com/onflow/flow-go/fvm/tracing"
    15  	"github.com/onflow/flow-go/model/flow"
    16  	"github.com/onflow/flow-go/module/trace"
    17  )
    18  
    19  type signatureType struct {
    20  	message []byte
    21  
    22  	errorBuilder func(flow.TransactionSignature, error) errors.CodedError
    23  
    24  	aggregateWeights map[flow.Address]int
    25  }
    26  
    27  type signatureEntry struct {
    28  	flow.TransactionSignature
    29  
    30  	signatureType
    31  }
    32  
    33  // signatureContinatuion is an internal/helper struct, accessible only by
    34  // TransactionVerifier, used to keep track of the signature verification
    35  // continuation state.
    36  type signatureContinuation struct {
    37  	// signatureEntry is the initial input.
    38  	signatureEntry
    39  
    40  	// accountKey is set by getAccountKeys().
    41  	accountKey flow.AccountPublicKey
    42  
    43  	// invokedVerify and verifyErr are set by verifyAccountSignatures().  Note
    44  	// that	verifyAccountSignatures() is always called after getAccountKeys()
    45  	// (i.e., accountKey is always initialized by the time
    46  	// verifyAccountSignatures is called).
    47  	invokedVerify bool
    48  	verifyErr     errors.CodedError
    49  }
    50  
    51  func (entry *signatureContinuation) newError(err error) errors.CodedError {
    52  	return entry.errorBuilder(entry.TransactionSignature, err)
    53  }
    54  
    55  func (entry *signatureContinuation) matches(
    56  	proposalKey flow.ProposalKey,
    57  ) bool {
    58  	return entry.Address == proposalKey.Address &&
    59  		entry.KeyIndex == proposalKey.KeyIndex
    60  }
    61  
    62  func (entry *signatureContinuation) verify() errors.CodedError {
    63  	if entry.invokedVerify {
    64  		return entry.verifyErr
    65  	}
    66  
    67  	entry.invokedVerify = true
    68  
    69  	valid, err := crypto.VerifySignatureFromTransaction(
    70  		entry.Signature,
    71  		entry.message,
    72  		entry.accountKey.PublicKey,
    73  		entry.accountKey.HashAlgo,
    74  	)
    75  	if err != nil {
    76  		entry.verifyErr = entry.newError(err)
    77  	} else if !valid {
    78  		entry.verifyErr = entry.newError(fmt.Errorf("signature is not valid"))
    79  	}
    80  
    81  	return entry.verifyErr
    82  }
    83  
    84  func newSignatureEntries(
    85  	payloadSignatures []flow.TransactionSignature,
    86  	payloadMessage []byte,
    87  	envelopeSignatures []flow.TransactionSignature,
    88  	envelopeMessage []byte,
    89  ) (
    90  	[]*signatureContinuation,
    91  	map[flow.Address]int,
    92  	map[flow.Address]int,
    93  	error,
    94  ) {
    95  	payloadWeights := make(map[flow.Address]int, len(payloadSignatures))
    96  	envelopeWeights := make(map[flow.Address]int, len(envelopeSignatures))
    97  
    98  	type pair struct {
    99  		signatureType
   100  		signatures []flow.TransactionSignature
   101  	}
   102  
   103  	list := []pair{
   104  		{
   105  			signatureType{
   106  				payloadMessage,
   107  				errors.NewInvalidPayloadSignatureError,
   108  				payloadWeights,
   109  			},
   110  			payloadSignatures,
   111  		},
   112  		{
   113  			signatureType{
   114  				envelopeMessage,
   115  				errors.NewInvalidEnvelopeSignatureError,
   116  				envelopeWeights,
   117  			},
   118  			envelopeSignatures,
   119  		},
   120  	}
   121  
   122  	numSignatures := len(payloadSignatures) + len(envelopeSignatures)
   123  	signatures := make([]*signatureContinuation, 0, numSignatures)
   124  
   125  	type uniqueKey struct {
   126  		address flow.Address
   127  		index   uint64
   128  	}
   129  	duplicate := make(map[uniqueKey]struct{}, numSignatures)
   130  
   131  	for _, group := range list {
   132  		for _, signature := range group.signatures {
   133  			entry := &signatureContinuation{
   134  				signatureEntry: signatureEntry{
   135  					TransactionSignature: signature,
   136  					signatureType:        group.signatureType,
   137  				},
   138  			}
   139  
   140  			key := uniqueKey{
   141  				address: signature.Address,
   142  				index:   signature.KeyIndex,
   143  			}
   144  
   145  			_, ok := duplicate[key]
   146  			if ok {
   147  				return nil, nil, nil, entry.newError(
   148  					fmt.Errorf("duplicate signatures are provided for the same key"))
   149  			}
   150  			duplicate[key] = struct{}{}
   151  			signatures = append(signatures, entry)
   152  		}
   153  	}
   154  
   155  	return signatures, payloadWeights, envelopeWeights, nil
   156  }
   157  
   158  // TransactionVerifier verifies the content of the transaction by
   159  // checking there is no double signature
   160  // all signatures are valid
   161  // all accounts provides enoguh weights
   162  //
   163  // if KeyWeightThreshold is set to a negative number, signature verification is skipped
   164  type TransactionVerifier struct {
   165  	VerificationConcurrency int
   166  }
   167  
   168  func (v *TransactionVerifier) CheckAuthorization(
   169  	tracer tracing.TracerSpan,
   170  	proc *TransactionProcedure,
   171  	txnState storage.TransactionPreparer,
   172  	keyWeightThreshold int,
   173  ) error {
   174  	// TODO(Janez): verification is part of inclusion fees, not execution fees.
   175  	var err error
   176  	txnState.RunWithAllLimitsDisabled(func() {
   177  		err = v.verifyTransaction(tracer, proc, txnState, keyWeightThreshold)
   178  	})
   179  	if err != nil {
   180  		return fmt.Errorf("transaction verification failed: %w", err)
   181  	}
   182  
   183  	return nil
   184  }
   185  
   186  // verifyTransaction verifies the transaction from the given procedure,
   187  // and check the Authorizers have enough weights.
   188  func (v *TransactionVerifier) verifyTransaction(
   189  	tracer tracing.TracerSpan,
   190  	proc *TransactionProcedure,
   191  	txnState storage.TransactionPreparer,
   192  	keyWeightThreshold int,
   193  ) error {
   194  	span := tracer.StartChildSpan(trace.FVMVerifyTransaction)
   195  	span.SetAttributes(
   196  		attribute.String("transaction.ID", proc.ID.String()),
   197  	)
   198  	defer span.End()
   199  
   200  	tx := proc.Transaction
   201  	if tx.Payer == flow.EmptyAddress {
   202  		return errors.NewInvalidAddressErrorf(tx.Payer, "payer address is invalid")
   203  	}
   204  
   205  	signatures, payloadWeights, envelopeWeights, err := newSignatureEntries(
   206  		tx.PayloadSignatures,
   207  		tx.PayloadMessage(),
   208  		tx.EnvelopeSignatures,
   209  		tx.EnvelopeMessage())
   210  	if err != nil {
   211  		return err
   212  	}
   213  
   214  	accounts := environment.NewAccounts(txnState)
   215  
   216  	if keyWeightThreshold < 0 {
   217  		return nil
   218  	}
   219  
   220  	err = v.getAccountKeys(txnState, accounts, signatures, tx.ProposalKey)
   221  	if err != nil {
   222  		return errors.NewInvalidProposalSignatureError(tx.ProposalKey, err)
   223  	}
   224  
   225  	err = v.verifyAccountSignatures(signatures)
   226  	if err != nil {
   227  		return errors.NewInvalidProposalSignatureError(tx.ProposalKey, err)
   228  	}
   229  
   230  	for _, addr := range tx.Authorizers {
   231  		// Skip this authorizer if it is also the payer. In the case where an account is
   232  		// both a PAYER as well as an AUTHORIZER or PROPOSER, that account is required
   233  		// to sign only the envelope.
   234  		if addr == tx.Payer {
   235  			continue
   236  		}
   237  		// hasSufficientKeyWeight
   238  		if !v.hasSufficientKeyWeight(payloadWeights, addr, keyWeightThreshold) {
   239  			return errors.NewAccountAuthorizationErrorf(
   240  				addr,
   241  				"authorizer account does not have sufficient signatures (%d < %d)",
   242  				payloadWeights[addr],
   243  				keyWeightThreshold)
   244  		}
   245  	}
   246  
   247  	if !v.hasSufficientKeyWeight(envelopeWeights, tx.Payer, keyWeightThreshold) {
   248  		// TODO change this to payer error (needed for fees)
   249  		return errors.NewAccountAuthorizationErrorf(
   250  			tx.Payer,
   251  			"payer account does not have sufficient signatures (%d < %d)",
   252  			envelopeWeights[tx.Payer],
   253  			keyWeightThreshold)
   254  	}
   255  
   256  	return nil
   257  }
   258  
   259  // getAccountKeys gets the signatures' account keys and populate the account
   260  // keys into the signature continuation structs.
   261  func (v *TransactionVerifier) getAccountKeys(
   262  	txnState storage.TransactionPreparer,
   263  	accounts environment.Accounts,
   264  	signatures []*signatureContinuation,
   265  	proposalKey flow.ProposalKey,
   266  ) error {
   267  	foundProposalSignature := false
   268  	for _, signature := range signatures {
   269  		accountKey, err := accounts.GetPublicKey(
   270  			signature.Address,
   271  			signature.KeyIndex)
   272  		if err != nil {
   273  			return signature.newError(err)
   274  		}
   275  
   276  		if accountKey.Revoked {
   277  			return signature.newError(
   278  				fmt.Errorf("account key has been revoked"))
   279  		}
   280  
   281  		signature.accountKey = accountKey
   282  
   283  		if !foundProposalSignature && signature.matches(proposalKey) {
   284  			foundProposalSignature = true
   285  		}
   286  	}
   287  
   288  	if !foundProposalSignature {
   289  		return fmt.Errorf(
   290  			"either the payload or the envelope should provide proposal " +
   291  				"signatures")
   292  	}
   293  
   294  	return nil
   295  }
   296  
   297  // verifyAccountSignatures verifies the given signature continuations and
   298  // aggregate the valid signatures' weights.
   299  func (v *TransactionVerifier) verifyAccountSignatures(
   300  	signatures []*signatureContinuation,
   301  ) error {
   302  	toVerifyChan := make(chan *signatureContinuation, len(signatures))
   303  	verifiedChan := make(chan *signatureContinuation, len(signatures))
   304  
   305  	verificationConcurrency := v.VerificationConcurrency
   306  	if len(signatures) < verificationConcurrency {
   307  		verificationConcurrency = len(signatures)
   308  	}
   309  
   310  	ctx, cancel := context.WithCancel(context.Background())
   311  	defer cancel()
   312  
   313  	wg := sync.WaitGroup{}
   314  	wg.Add(verificationConcurrency)
   315  
   316  	for i := 0; i < verificationConcurrency; i++ {
   317  		go func() {
   318  			defer wg.Done()
   319  
   320  			for entry := range toVerifyChan {
   321  				err := entry.verify()
   322  
   323  				verifiedChan <- entry
   324  
   325  				if err != nil {
   326  					// Signal to other workers to early exit
   327  					cancel()
   328  					return
   329  				}
   330  
   331  				select {
   332  				case <-ctx.Done():
   333  					// Another worker has error-ed out.
   334  					return
   335  				default:
   336  					// continue
   337  				}
   338  			}
   339  		}()
   340  	}
   341  
   342  	for _, entry := range signatures {
   343  		toVerifyChan <- entry
   344  	}
   345  	close(toVerifyChan)
   346  
   347  	foundError := false
   348  	for i := 0; i < len(signatures); i++ {
   349  		entry := <-verifiedChan
   350  
   351  		if !entry.invokedVerify {
   352  			// This is a programming error.
   353  			return fmt.Errorf("signatureContinuation.verify not called")
   354  		}
   355  
   356  		if entry.verifyErr != nil {
   357  			// Unfortunately, we cannot return the first error we received
   358  			// from the verifiedChan since the entries may be out of order,
   359  			// which could lead to non-deterministic error output.
   360  			foundError = true
   361  			break
   362  		}
   363  
   364  		entry.aggregateWeights[entry.Address] += entry.accountKey.Weight
   365  	}
   366  
   367  	if !foundError {
   368  		return nil
   369  	}
   370  
   371  	// We need to wait for all workers to finish in order to deterministically
   372  	// return the first error with respect to the signatures slice.
   373  
   374  	wg.Wait()
   375  
   376  	for _, entry := range signatures {
   377  		if entry.verifyErr != nil {
   378  			return entry.verifyErr
   379  		}
   380  	}
   381  
   382  	panic("Should never reach here")
   383  }
   384  
   385  func (v *TransactionVerifier) hasSufficientKeyWeight(
   386  	weights map[flow.Address]int,
   387  	address flow.Address,
   388  	keyWeightThreshold int,
   389  ) bool {
   390  	return weights[address] >= keyWeightThreshold
   391  }