-
Notifications
You must be signed in to change notification settings - Fork 180
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
Changes from 18 commits
a0bd8b1
6073629
ee604c9
8633924
1cff2ca
f48b96c
7c3eecf
2e08c7e
48a886b
d75823c
2c992a3
ffd442c
ee0f242
3df7bc7
31146ae
f5105dd
e1bb4a6
c30b2a2
ee21e3b
dcd8a01
88db7a1
1041c48
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
|
@@ -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 { | ||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are all keys already checked and added using This is the same as above. I suggested to panic and Janez 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it is for performance. Here are the tests https://github.com/onflow/flow-go/blob/master/fvm/transactionVerifier_test.go#L43-L68