drandverify.gno
4.26 Kb · 103 lines
1package bls254
2
3// BLS verification for drand's bls-bn254-unchained-on-g1 beacon.
4//
5// The pairing equation is the same one the existing SPLASH Verify uses --
6// signatures in G1, public keys in G2, e(sig, G2gen) * e(-H(m), pk) == 1 -- so
7// the only thing that differs is how H(m) is built. That is not a small
8// difference: see drandcurve.gno.
9//
10// The G2 generator is shared, and that is not an assumption. drand/kyber
11// v1.3.2's twistGen, decoded from its Montgomery form and written in EIP-197
12// imaginary-first order (x_imag|x_real|y_imag|y_real), is byte-for-byte the
13// g2GenHex constant already in bls254.gno. The published evmnet group key is
14// sk*twistGen for the DKG's collective secret, so pairing against g2GenBytes is
15// correct for this chain.
16
17import "crypto/bn254"
18
19// verifyPairingG1SigG2Key reports whether e(sig, G2gen) == e(h, pubkey), for a
20// 64-byte G1 signature, a 64-byte G1 message point and a 128-byte G2 key in
21// EIP-197 wire order.
22//
23// This DUPLICATES the tail of Verify rather than refactoring Verify to call it.
24// The brief allowed either and asked which and why: the live SPLASH beacon
25// depends on Verify, the instruction I was given was not to alter an existing
26// exported function, and a behaviour-preserving refactor is still an edit to
27// code that is in production. Twelve duplicated lines are the cheaper risk.
28//
29// The duplication is pinned against drift by
30// TestVerifyPairingHelperMatchesVerify, which asserts this helper and Verify
31// return the same answer for every vector and every reject-vector of the
32// existing SPLASH suite. If someone edits one and not the other, that test
33// fails.
34func verifyPairingG1SigG2Key(pubkey, h, sig []byte) bool {
35 if len(pubkey) != 128 || len(sig) != 64 || len(h) != 64 {
36 return false
37 }
38 negH := negateG1(h)
39 if negH == nil {
40 return false
41 }
42
43 in := make([]byte, 0, 2*bn254.PairingChunkSize)
44 in = append(in, sig...)
45 in = append(in, g2GenBytes...)
46 in = append(in, negH...)
47 in = append(in, pubkey...)
48 if len(in) != 2*bn254.PairingChunkSize {
49 return false
50 }
51
52 out := bn254.PairingCheck(in)
53 // PairingCheck returns an empty NON-NIL slice on an invalid point or an
54 // unreduced coordinate; checking length (not nilness) is load-bearing.
55 if len(out) != bn254.PairingOutputSize {
56 return false
57 }
58 return out[len(out)-1] == 1
59}
60
61// VerifyDrand reports whether sig is the drand beacon signature for round under
62// pubKey, for the bls-bn254-unchained-on-g1 scheme (drand's "evmnet" chain,
63// hash 04f1e9062b8a81f848fded9c12306733282b2727ecced50032187751166ec8c3).
64//
65// pubKey is the 128-byte G2 group public key in EIP-197 wire order
66// (x_imag|x_real|y_imag|y_real), which is exactly the byte string the drand
67// /info endpoint serves as "public_key". sig is the 64-byte G1 signature from
68// /public/<round>. The chain is unchained, so no previous signature is needed.
69//
70// The randomness for a verified round is sha256(sig) -- the same derivation
71// r/splash/beacon already uses. This function deliberately does not compute it:
72// verifying and deriving are separate steps and a caller that conflates them
73// tends to derive from an unverified signature.
74//
75// VerifyDrand is total. It never panics, and returns false for every malformed
76// input: wrong pubKey or sig length, a negative round, a signature coordinate at
77// or above p, an off-curve point, and an invalid precompile result.
78//
79// This is NOT Verify. Verify implements the SPLASH beacon's own scheme
80// (chainID || "|" || decimal(round), SHA-256, try-and-increment hash-to-G1) and
81// will reject every real drand signature; VerifyDrand will reject every SPLASH
82// one. Pick by which beacon produced the signature, never by which is to hand.
83func VerifyDrand(pubKey []byte, round int64, sig []byte) bool {
84 if len(pubKey) != 128 || len(sig) != 64 {
85 return false
86 }
87 // Reject unreduced signature coordinates explicitly. bn254.PairingCheck
88 // would also reject them, but relying on a native's error behaviour for a
89 // validity rule the caller can violate is how that rule stops being checked.
90 if cmp(toLimbs(sig[0:32]), pLimbs) >= 0 || cmp(toLimbs(sig[32:64]), pLimbs) >= 0 {
91 return false
92 }
93
94 msg := DrandRoundMessage(round)
95 if msg == nil {
96 return false // negative round
97 }
98 h := HashToG1Drand(msg)
99 if h == nil {
100 return false
101 }
102 return verifyPairingG1SigG2Key(pubKey, h, sig)
103}