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}