Skip to content

Latest commit

 

History

History
154 lines (116 loc) · 4.25 KB

022.md

File metadata and controls

154 lines (116 loc) · 4.25 KB

Active Wintergreen Porpoise

High

Incorrect Y Coordinate Check will cause signature verification to fail unexpectedly for legitimate signatures.

Summary

The misplaced Y coordinate check in encVerify will cause signature verification to fail unexpectedly for legitimate signatures as the check is performed on the wrong point.

func encVerify(
	sig *AdaptorSignature,
	m []byte,
	pubKeyBytes []byte,
	t *btcec.JacobianPoint,
) error {
	// Fail if m is not 32 bytes
	if len(m) != chainhash.HashSize {
		return fmt.Errorf("wrong size for message (got %v, want %v)",
			len(m), chainhash.HashSize)
	}

	// R' = R-T (or R+T if it needs negation)
	R := &sig.r // NOTE: R is an affine point
	var RHat btcec.JacobianPoint
	if sig.needNegation {
		btcec.AddNonConst(R, t, &RHat)
	} else {
		btcec.AddNonConst(R, negatePoint(t), &RHat)
	}

	RHat.ToAffine()

	...

	expRHat.ToAffine()

	if R.Y.IsOdd() { //-----------> The misplaced Y coordinate check 
		return fmt.Errorf("expected R.y is odd")
	}

Root Cause

In encVerify the Y coordinate check if RHat.Y.IsOdd() is performed on RHat before the point R is checked. The correct order is to check R first, then RHat after the affine conversion.

Internal Pre-conditions

  1. A valid adaptor signature needs to be generated.
  2. The encVerify function needs to be called with this signature.

External Pre-conditions

None

Attack Path

  1. A valid adaptor signature is generated for a given message and public key.
  2. The encVerify function is called to verify the signature.
  3. The encVerify function checks RHat.Y.IsOdd()
  4. If RHat.Y happens to be odd, the verification will fail even if the signature is valid.

Impact

Legitimate users will be unable to have their signatures verified, preventing them from performing actions that require a valid signature.

PoC

No response

Mitigation

Moved the R.Y.IsOdd()check to RHat.Y.IsOdd() and placed it after RHat.ToAffine()

func encVerify(
    sig *AdaptorSignature,
    m []byte,
    pubKeyBytes []byte,
    t *btcec.JacobianPoint,
) error {
    // Fail if m is not 32 bytes
    if len(m) != chainhash.HashSize {
        return fmt.Errorf("wrong size for message (got %v, want %v)",
            len(m), chainhash.HashSize)
    }

    // R' = R-T (or R+T if it needs negation)
    R := &sig.r // NOTE: R is an affine point

    var RHat btcec.JacobianPoint
    if sig.needNegation {
        btcec.AddNonConst(R, t, &RHat)
    } else {
        btcec.AddNonConst(R, negatePoint(t), &RHat)
    }

    RHat.ToAffine()

    // Check if RHat.Y is odd after converting to affine coordinates
    if RHat.Y.IsOdd() {
        return fmt.Errorf("expected RHat.y is odd")
    }

    // P = lift_x(int(pk))
    pubKey, err := schnorr.ParsePubKey(pubKeyBytes)
    if err != nil {
        return err
    }
    // Fail if P is not a point on the curve
    if !pubKey.IsOnCurve() {
        return fmt.Errorf("pubkey point is not on curve")
    }

    // e = int(tagged_hash("BIP0340/challenge", bytes(R) || bytes(P) || M)) mod n.
    var rBytes [chainhash.HashSize]byte
    R.X.PutBytesUnchecked(rBytes[:])
    pBytes := schnorr.SerializePubKey(pubKey)
    commitment := chainhash.TaggedHash(
        chainhash.TagBIP0340Challenge, rBytes[:], pBytes, m,
    )
    var e btcec.ModNScalar
    e.SetBytes((*[ModNScalarSize]byte)(commitment))

    // Negate e here so we can use AddNonConst below to subtract the s'*G
    // point from e*P.
    e.Negate()

    // expected R' = s'*G - e*P
    var P, expRHat, sHatG, eP btcec.JacobianPoint
    pubKey.AsJacobian(&P)
    btcec.ScalarBaseMultNonConst(&sig.sHat, &sHatG) // s'*G
    btcec.ScalarMultNonConst(&e, &P, &eP)           // -e*P
    btcec.AddNonConst(&sHatG, &eP, &expRHat)        // R' = s'*G-e*P

    // Fail if expected R' is the point at infinity
    if (expRHat.X.IsZero() && expRHat.Y.IsZero()) || expRHat.Z.IsZero() {
        return fmt.Errorf("expected R' point is at infinity")
    }

    expRHat.ToAffine()

    // ensure R' is same as the expected R' = s'*G - e*P
    if !expRHat.X.Equals(&RHat.X) {
        return fmt.Errorf("expected R' = s'*G - e*P is different from the actual R'")
    }

    return nil
}