package stm import ( "crypto/subtle" "fmt" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" ) // BLS12-381 ciphersuite DST — Mithril uses an EMPTY DST. The Rust impl // calls `blst_sig.verify(sig_gc, msg, &[], &[], pk, pk_gc)` with an // empty `dst` slice. hash-to-curve uses no domain separation. Any // non-empty DST here breaks interop. var blsDST = []byte{} // BlsVerify validates a BLS12-381 min_sig signature: // // e(sig, G2_gen) == e(H(msg), vk) // // sig must be 48 bytes (compressed G1), vk must be 96 bytes (compressed G2). // Returns nil on success or a typed error on any failure. func BlsVerify(msg, sig, vk []byte) error { if len(sig) != bls12381.SizeOfG1AffineCompressed { return fmt.Errorf("sig wrong size: got %d, want %d", len(sig), bls12381.SizeOfG1AffineCompressed) } if len(vk) != bls12381.SizeOfG2AffineCompressed { return fmt.Errorf("vk wrong size: got %d, want %d", len(vk), bls12381.SizeOfG2AffineCompressed) } var sigPt bls12381.G1Affine if _, err := sigPt.SetBytes(sig); err != nil { return fmt.Errorf("sig decode: %w", err) } // Subgroup check — critical to prevent small-subgroup attacks. if !sigPt.IsInSubGroup() { return fmt.Errorf("sig not in prime-order subgroup") } var vkPt bls12381.G2Affine if _, err := vkPt.SetBytes(vk); err != nil { return fmt.Errorf("vk decode: %w", err) } if !vkPt.IsInSubGroup() { return fmt.Errorf("vk not in prime-order subgroup") } // Reject the neutral element as a verification key (would accept anything). var zero bls12381.G2Affine zeroBytes := zero.Bytes() vkBytes := vkPt.Bytes() if subtle.ConstantTimeCompare(vkBytes[:], zeroBytes[:]) == 1 { return fmt.Errorf("vk is the identity element") } // H(msg) into G1 hPt, err := bls12381.HashToG1(msg, blsDST) if err != nil { return fmt.Errorf("hash-to-G1: %w", err) } // G2 generator _, _, _, g2Gen := bls12381.Generators() // Pairing equation: e(sig, G2_gen) * e(-H(msg), vk) == 1 // gnark's PairingCheck returns true iff the product is 1. We negate // H(msg) on the G1 side so the equation collapses to identity. var negH bls12381.G1Affine negH.Neg(&hPt) ok, err := bls12381.PairingCheck( []bls12381.G1Affine{sigPt, negH}, []bls12381.G2Affine{g2Gen, vkPt}, ) if err != nil { return fmt.Errorf("pairing: %w", err) } if !ok { return fmt.Errorf("BLS signature invalid") } return nil }