Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FVM] signature verification refactoring #2171

Merged
merged 22 commits into from
May 10, 2022
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions access/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,13 +267,16 @@ func (v *TransactionValidator) checkAddresses(tx *flow.TransactionBody) error {

// every key (account, key index combination) can only be used once for signing
func (v *TransactionValidator) checkSignatureDuplications(tx *flow.TransactionBody) error {
observedSigs := make(map[string]bool)
type uniqueKey struct {
address flow.Address
index uint64
}
observedSigs := make(map[uniqueKey]bool)
for _, sig := range append(tx.PayloadSignatures, tx.EnvelopeSignatures...) {
keyStr := sig.UniqueKeyString()
if observedSigs[keyStr] {
if observedSigs[uniqueKey{sig.Address, sig.KeyIndex}] {
return DuplicatedSignatureError{Address: sig.Address, KeyIndex: sig.KeyIndex}
}
observedSigs[keyStr] = true
observedSigs[uniqueKey{sig.Address, sig.KeyIndex}] = true
Comment on lines -270 to +279
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this change is mostly for performance reasons, could you please check we have tests for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
return nil
}
Expand Down
3 changes: 0 additions & 3 deletions fvm/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package fvm
import (
"github.com/rs/zerolog"

"github.com/onflow/flow-go/fvm/crypto"
"github.com/onflow/flow-go/fvm/handler"
"github.com/onflow/flow-go/fvm/state"
"github.com/onflow/flow-go/model/flow"
Expand Down Expand Up @@ -32,7 +31,6 @@ type Context struct {
ServiceEventCollectionEnabled bool
AccountFreezeAvailable bool
ExtensiveTracing bool
SignatureVerifier crypto.SignatureVerifier
ramtinms marked this conversation as resolved.
Show resolved Hide resolved
TransactionProcessors []TransactionProcessor
ScriptProcessors []ScriptProcessor
Logger zerolog.Logger
Expand Down Expand Up @@ -84,7 +82,6 @@ func defaultContext(logger zerolog.Logger) Context {
ServiceEventCollectionEnabled: false,
AccountFreezeAvailable: false,
ExtensiveTracing: false,
SignatureVerifier: crypto.NewDefaultSignatureVerifier(),
TransactionProcessors: []TransactionProcessor{
NewTransactionAccountFrozenChecker(),
NewTransactionSignatureVerifier(AccountKeyWeightThreshold),
Expand Down
114 changes: 66 additions & 48 deletions fvm/crypto/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,18 @@ func ValidatePublicKey(signAlgo runtime.SignatureAlgorithm, pk []byte) error {
return nil
}

// VerifySignatureFromRuntime is an adapter that performs signature verification using
// raw values provided by the Cadence runtime.
// VerifySignatureFromRuntime performs signature verification using raw values provided
// by the Cadence runtime.
//
// The signature/hash function combinations accepted are:
// - ECDSA (on both curves P-256 and secp256k1) with any of SHA2-256/SHA3-256/Keccak256.
// - BLS (on BLS12-381 curve) with the specific KMAC128 for BLS.
// The tag is applied to the message depending on the hash function used.
//
// The function errors:
// - NewValueErrorf for any user error
// - panic for any other unexpected error
func VerifySignatureFromRuntime(
verifier SignatureVerifier,
signature []byte,
tag string,
message []byte,
Expand All @@ -146,11 +154,7 @@ func VerifySignatureFromRuntime(
return false, errors.NewValueErrorf(sigAlgo.String(), "cannot use hashing algorithm type %s with signature signature algorithm type %s",
hashAlgo, sigAlgo)
}

// tag length compatibility
if len(tag) > flow.DomainTagLength {
return false, errors.NewValueErrorf(tag, "tag length (%d) is larger than max length allowed (%d bytes).", len(tag), flow.DomainTagLength)
}
// tag constraints are checked when initializing a prefix-hasher
ramtinms marked this conversation as resolved.
Show resolved Hide resolved

// check BLS compatibilites
} else if sigAlgo == crypto.BLSBLS12381 && hashAlgo != hash.KMAC128 {
Expand All @@ -160,71 +164,85 @@ func VerifySignatureFromRuntime(
// there are no tag constraints
}

// decode the public key
publicKey, err := crypto.DecodePublicKey(sigAlgo, rawPublicKey)
if err != nil {
return false, errors.NewValueErrorf(hex.EncodeToString(rawPublicKey), "cannot decode public key: %w", err)
}

valid, err := verifier.Verify(
signature,
tag,
message,
publicKey,
hashAlgo,
)
// create a hasher
var hasher hash.Hasher
switch hashAlgo {
case hash.SHA2_256, hash.SHA3_256, hash.Keccak_256:
var err error
if hasher, err = NewPrefixedHashing(hashAlgo, tag); err != nil {
return false, errors.NewValueErrorf(err.Error(), "runtime verification failed")
}
case hash.KMAC128:
hasher = crypto.NewBLSKMAC(tag)
default:
return false, errors.NewValueErrorf(fmt.Sprint(hashAlgo), "hashing algorithm type not found")
}

valid, err := publicKey.Verify(signature, message, hasher)
if err != nil {
return false, err
// All inputs are guaranteed to be valid at this stage.
// The check for crypto.InvalidInputs is only a sanity check
if crypto.IsInvalidInputsError(err) {
return false, err
}
panic(fmt.Errorf("verify runtime signature failed with unexpected error %w", err))
}

return valid, nil
}

type SignatureVerifier interface {
Verify(
signature []byte,
tag string,
message []byte,
publicKey crypto.PublicKey,
hashAlgo hash.HashingAlgorithm,
) (bool, error)
}

type DefaultSignatureVerifier struct{}

func NewDefaultSignatureVerifier() DefaultSignatureVerifier {
return DefaultSignatureVerifier{}
}

func (DefaultSignatureVerifier) Verify(
// VerifySignatureFromRuntime performs signature verification using raw values provided
// by the Cadence runtime.
//
// The signature/hash function combinations accepted are:
// - ECDSA (on both curves P-256 and secp256k1) with any of SHA2-256/SHA3-256.
// The tag is applied to the message as a constant length prefix.
//
// The function errors:
// - NewValueErrorf for any user error
// - panic for any other unexpected error
func VerifySignatureFromTransaction(
signature []byte,
tag string,
message []byte,
publicKey crypto.PublicKey,
pk crypto.PublicKey,
hashAlgo hash.HashingAlgorithm,
) (bool, error) {

var hasher hash.Hasher
// check ECDSA compatibilites
if pk.Algorithm() != crypto.ECDSAP256 && pk.Algorithm() != crypto.ECDSASecp256k1 {
// TODO: check if we should panic
// This case only happens in production if there is a bug
Comment on lines +218 to +220
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably run some analysis on the mainnet data to verify all data are okey

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are all keys already checked and added using AddAccountKey, so this check is only a sanity check, and if it fails then we have am internal bug somewhere.

This is the same as above. I suggested to panic and Janez suggest Failure error. What do you suggest?

return false, errors.NewUnknownFailure(fmt.Errorf(
pk.Algorithm().String(), "is not supported in transactions"))
}
// hashing compatibility
if hashAlgo != hash.SHA2_256 && hashAlgo != hash.SHA3_256 {
// TODO: check if we should panic
// This case only happens in production if there is a bug
return false, errors.NewUnknownFailure(fmt.Errorf(
hashAlgo.String(), "is not supported in transactions"))
}
Comment on lines +228 to +230
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure about this, you mean if there is some problematic old keys we would stop the network? I think we should just error and add a watch on the error type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tagged you about this here. I suggested to panic, Janez suggested the Failure error. As mentioned in the comment, that case can only happen if there is a bug in the code. These keys are all account keys, they are supposed to be checked and added previously, and they are not supposed to be problematic.

What do you suggest to use here?


switch hashAlgo {
case hash.SHA2_256, hash.SHA3_256, hash.Keccak_256:
var err error
if hasher, err = NewPrefixedHashing(hashAlgo, tag); err != nil {
return false, errors.NewValueErrorf(err.Error(), "verification failed")
}
case hash.KMAC128:
hasher = crypto.NewBLSKMAC(tag)
default:
return false, errors.NewValueErrorf(fmt.Sprint(hashAlgo), "hashing algorithm type not found")
hasher, err := NewPrefixedHashing(hashAlgo, flow.TransactionTagString)
if err != nil {
return false, errors.NewValueErrorf(err.Error(), "transaction verification failed")
}

valid, err := publicKey.Verify(signature, message, hasher)
valid, err := pk.Verify(signature, message, hasher)
if err != nil {
// All inputs are guaranteed to be valid at this stage.
// The check for crypto.InvalidInputs is only a sanity check
if crypto.IsInvalidInputsError(err) {
return false, err
}
panic(fmt.Errorf("verify signature failed with unexpected error %w", err))
// TODO: check if we should panic or bubble the error up
panic(fmt.Errorf("verify transaction signature failed with unexpected error %w", err))
tarakby marked this conversation as resolved.
Show resolved Hide resolved
}

return valid, nil
Expand Down
Loading