Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

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}