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 }