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.

drandmsg.gno

5.91 Kb · 148 lines
  1package bls254
  2
  3// RFC 9380 expand_message_xmd, and drand's round-message encoding.
  4//
  5// These are the first two pieces of the drand verification path. They are
  6// separate from the SPLASH beacon's own encoding on purpose: the SPLASH scheme
  7// hashes chainID || "|" || decimal(round) with SHA-256 and maps to G1 by
  8// try-and-increment, and drand's evmnet chain does neither of those things. Two
  9// schemes, two names, no shared helpers that could be pointed at the wrong one.
 10//
 11// Everything here is verified against drand's own source rather than against
 12// documentation:
 13//
 14//	drand v2.1.7 crypto/schemes.go, NewPedersenBLSBN254UnchainedOnG1Scheme:
 15//	  Pairing.SetDomainG1([]byte("BLS_SIG_BN254G1_XMD:KECCAK-256_SVDW_RO_NUL_"))
 16//	  DigestFunc = keccak256(binary.BigEndian uint64 round)
 17//
 18// drand/kyber's *default* BN254 G1 DST is "BN254G1_XMD:KECCAK-256_SSWU_RO_" --
 19// SSWU, a different map and a different string. It is not what this chain signs
 20// under, and using it makes every signature fail.
 21
 22import (
 23	"crypto/keccak256"
 24	"crypto/sha256"
 25)
 26
 27// DrandDST is the hash-to-curve domain separation tag for drand's
 28// bls-bn254-unchained-on-g1 scheme (the "evmnet" beacon, chain hash
 29// 04f1e9062b8a81f848fded9c12306733282b2727ecced50032187751166ec8c3).
 30const DrandDST = "BLS_SIG_BN254G1_XMD:KECCAK-256_SVDW_RO_NUL_"
 31
 32// keccakBlockSize is Keccak-256's input block size, i.e. its sponge rate:
 33// 1600 bits of state minus 2*256 bits of capacity = 1088 bits = 136 bytes.
 34// RFC 9380's Z_pad is I2OSP(0, s_in_bytes) with s_in_bytes = this value.
 35//
 36// 136, not 64. SHA-2's 64-byte block is the number most expand_message_xmd
 37// implementations are written against, and drand/kyber, the randa-mu Solidity
 38// reference and gnark all use the hash's own BlockSize() here, which for
 39// Keccak-256 is 136.
 40const keccakBlockSize = 136
 41
 42// sha256BlockSize is SHA-256's input block size, used only by the tests that
 43// replay RFC 9380's published SHA-256 expand_message_xmd vectors.
 44const sha256BlockSize = 64
 45
 46// expandMessageXMDWith implements expand_message_xmd from RFC 9380 section
 47// 5.3.1, parameterised on the hash and on that hash's input block size.
 48//
 49// The parameterisation exists for one reason: RFC 9380 publishes
 50// expand_message_xmd test vectors for SHA-256 and SHA-512 but not for
 51// Keccak-256, so the only way to check this construction against the RFC's own
 52// numbers is to run it with SHA-256. TestExpandMessageXMDRFC9380 does exactly
 53// that. Production callers go through expandMessageXMD, which fixes the hash to
 54// Keccak-256.
 55//
 56// Returns nil rather than a short or wrong answer on any input the RFC forbids:
 57// n outside [1, 65535], ell above 255, or a DST longer than 255 bytes.
 58func expandMessageXMDWith(sum func([]byte) [32]byte, sInBytes int, msg, dst []byte, n int) []byte {
 59	if n <= 0 || n > 65535 || len(dst) > 255 || sInBytes <= 0 {
 60		return nil
 61	}
 62	const bInBytes = 32 // both SHA-256 and Keccak-256 produce 32 bytes
 63	ell := (n + bInBytes - 1) / bInBytes
 64	if ell > 255 {
 65		return nil
 66	}
 67
 68	// DST_prime = DST || I2OSP(len(DST), 1)
 69	dstPrime := make([]byte, 0, len(dst)+1)
 70	dstPrime = append(dstPrime, dst...)
 71	dstPrime = append(dstPrime, byte(len(dst)))
 72
 73	// b_0 = H(Z_pad || msg || I2OSP(n, 2) || I2OSP(0, 1) || DST_prime)
 74	buf := make([]byte, 0, sInBytes+len(msg)+3+len(dstPrime))
 75	buf = append(buf, make([]byte, sInBytes)...) // Z_pad
 76	buf = append(buf, msg...)
 77	buf = append(buf, byte(n>>8), byte(n)) // l_i_b_str = I2OSP(n, 2)
 78	buf = append(buf, 0)                   // I2OSP(0, 1)
 79	buf = append(buf, dstPrime...)
 80	b0 := sum(buf)
 81
 82	// b_1 = H(b_0 || I2OSP(1, 1) || DST_prime)
 83	buf = make([]byte, 0, bInBytes+1+len(dstPrime))
 84	buf = append(buf, b0[:]...)
 85	buf = append(buf, 1)
 86	buf = append(buf, dstPrime...)
 87	bi := sum(buf)
 88
 89	out := make([]byte, 0, ell*bInBytes)
 90	out = append(out, bi[:]...)
 91
 92	// b_i = H(strxor(b_0, b_(i-1)) || I2OSP(i, 1) || DST_prime)
 93	for i := 2; i <= ell; i++ {
 94		var x [bInBytes]byte
 95		for j := 0; j < bInBytes; j++ {
 96			x[j] = b0[j] ^ bi[j]
 97		}
 98		buf = make([]byte, 0, bInBytes+1+len(dstPrime))
 99		buf = append(buf, x[:]...)
100		buf = append(buf, byte(i))
101		buf = append(buf, dstPrime...)
102		bi = sum(buf)
103		out = append(out, bi[:]...)
104	}
105	return out[:n]
106}
107
108// expandMessageXMD is expand_message_xmd with H = legacy Keccak-256, which is
109// what drand's bn254 scheme uses. Returns n pseudorandom bytes, or nil on input
110// the RFC forbids (see expandMessageXMDWith).
111//
112// Legacy Keccak-256, NOT NIST SHA3-256: the padding differs, and crypto/keccak256
113// exists as its own package in the gno stdlib for exactly that reason.
114func expandMessageXMD(msg, dst []byte, n int) []byte {
115	return expandMessageXMDWith(keccak256.Sum256, keccakBlockSize, msg, dst, n)
116}
117
118// expandMessageXMDSHA256 is expand_message_xmd with H = SHA-256. It exists so
119// the tests can replay RFC 9380 appendix K.1, and is deliberately not used by
120// any verification path.
121func expandMessageXMDSHA256(msg, dst []byte, n int) []byte {
122	return expandMessageXMDWith(sha256.Sum256, sha256BlockSize, msg, dst, n)
123}
124
125// DrandRoundMessage returns the message drand's bls-bn254-unchained-on-g1
126// scheme signs for a round: keccak256 of the round number as a big-endian
127// uint64. That is drand v2.1.7's DigestBeacon for this scheme, verbatim.
128//
129// The chain is *unchained*: the previous round's signature is not part of the
130// message, so any round can be verified on its own.
131//
132// Returns nil for a negative round. drand round numbers are uint64 and start at
133// 1; a negative value is a caller bug, and silently reinterpreting it as a huge
134// unsigned round would produce a message for a round nobody will ever sign.
135// Round 0 is encoded normally -- it is a well-defined byte string -- but no
136// signature exists for it, so verification will simply fail.
137func DrandRoundMessage(round int64) []byte {
138	if round < 0 {
139		return nil
140	}
141	u := uint64(round)
142	b := []byte{
143		byte(u >> 56), byte(u >> 48), byte(u >> 40), byte(u >> 32),
144		byte(u >> 24), byte(u >> 16), byte(u >> 8), byte(u),
145	}
146	h := keccak256.Sum256(b)
147	return h[:]
148}