Macho Merlot Squirrel
Medium
The BLS signature verification function returning nil
error regardless of verification status will cause a security bypass vulnerability for downstream consumers as developers will incorrectly validate signatures by only checking error return values and not boolean results.
In babylon/crypto/bls12381/bls.go:55-60 the Verify
function returns a boolean indicating signature validity but always returns nil error regardless of verification outcome, breaking the common Go pattern where errors indicate validation failures:
func Verify(sig Signature, pk PublicKey, msg []byte) (bool, error) {
dummySig := new(BlsSig)
return dummySig.VerifyCompressed(sig, false, pk, false, msg, DST), nil
}
- A developer needs to use the BLS
Verify
function to validate signatures - Developer must follow the common Go pattern of only checking error returns and not the boolean result
None
- Attacker creates an invalid BLS signature
- Victim code calls
Verify(invalid_sig, pk, msg)
which returns false, nil - Victim code only checks if
err == nil
and assumes verification succeeded - Application proceeds as if the signature was valid, bypassing crucial security checks
The application security is compromised as unauthorized data or transactions can be processed with invalid signatures. This could lead to privilege escalation, unauthorized access, or financial losses depending on what the signature verification was protecting.
// Add to file: babylon/crypto/bls12381/bls_test.go
func TestVerifyApiMisuseVulnerability(t *testing.T) {
// Generate a valid key pair
sk, pk := GenKeyPair()
message := []byte("test message")
// Create a valid signature
validSig := Sign(sk, message)
// Create an invalid signature (by signing different message)
differentMessage := []byte("different message")
invalidSig := Sign(sk, differentMessage)
// Demonstrate proper usage - checks the boolean result
validResult, err := Verify(validSig, pk, message)
require.Nil(t, err)
require.True(t, validResult)
t.Logf("PROPER USAGE - Valid signature check: result=%v, err=%v", validResult, err)
invalidResult, err := Verify(invalidSig, pk, message)
require.Nil(t, err)
require.False(t, invalidResult)
t.Logf("PROPER USAGE - Invalid signature check: result=%v, err=%v", invalidResult, err)
// VULNERABILITY: Verify() always returns nil error regardless of signature validity!
// This shows that checking only the error is insufficient and dangerous
t.Log("=============== VULNERABILITY DEMONSTRATION ===============")
// Vulnerable code pattern - only checks error
isValid := func(sig Signature, pk PublicKey, msg []byte) bool {
result, err := Verify(sig, pk, msg)
t.Logf("Inside vulnerable function: result=%v, err=%v", result, err)
return err == nil // This ALWAYS returns true, regardless of signature validity!
}
// Test with valid signature
validCheck := isValid(validSig, pk, message)
t.Logf("VULNERABLE CHECK with valid signature: returned %v (expected true)", validCheck)
require.True(t, validCheck)
// Test with invalid signature - THIS IS THE VULNERABILITY!
invalidCheck := isValid(invalidSig, pk, message)
t.Logf("VULNERABLE CHECK with invalid signature: returned %v (should be false!)", invalidCheck)
require.True(t, invalidCheck, "SECURITY VULNERABILITY: Invalid signature appears valid when only checking error!")
t.Log("This vulnerability could lead to accepting invalid signatures if code only checks for nil errors")
}
Result example:
Running tool: /opt/homebrew/bin/go test -timeout 30s -run ^TestVerifyApiMisuseVulnerability$ github.com/babylonlabs-io/babylon/crypto/bls12381
=== RUN TestVerifyApiMisuseVulnerability
@root/babylon/crypto/bls12381/bls_test.go:122: PROPER USAGE - Valid signature check: result=true, err=<nil>
@root/babylon/crypto/bls12381/bls_test.go:127: PROPER USAGE - Invalid signature check: result=false, err=<nil>
@root/babylon/crypto/bls12381/bls_test.go:132: =============== VULNERABILITY DEMONSTRATION ===============
@root/babylon/crypto/bls12381/bls_test.go:137: Inside vulnerable function: result=true, err=<nil>
@root/babylon/crypto/bls12381/bls_test.go:143: VULNERABLE CHECK with valid signature: returned true (expected true)
@root/babylon/crypto/bls12381/bls_test.go:137: Inside vulnerable function: result=false, err=<nil>
@root/babylon/crypto/bls12381/bls_test.go:148: VULNERABLE CHECK with invalid signature: returned true (should be false!)
@root/babylon/crypto/bls12381/bls_test.go:151: This vulnerability could lead to accepting invalid signatures if code only checks for nil errors
--- PASS: TestVerifyApiMisuseVulnerability (0.01s)
PASS
ok github.com/babylonlabs-io/babylon/crypto/bls12381 0.394s
Modify the BLS Verify
function to follow standard Go conventions by returning an error when verification fails:
func Verify(sig Signature, pk PublicKey, msg []byte) (bool, error) {
dummySig := new(BlsSig)
result := dummySig.VerifyCompressed(sig, false, pk, false, msg, DST)
if !result {
return false, errors.New("BLS signature verification failed")
}
return true, nil
}
Alternatively, simplify the API to avoid confusion:
func Verify(sig Signature, pk PublicKey, msg []byte) error {
dummySig := new(BlsSig)
result := dummySig.VerifyCompressed(sig, false, pk, false, msg, DST)
if !result {
return errors.New("BLS signature verification failed")
}
return nil
}