forked from onflow/flow-go
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbls_crossBLST_test.go
223 lines (188 loc) · 8.93 KB
/
bls_crossBLST_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
//go:build relic
// +build relic
package crypto
// This file contains tests against the library BLST (https://github.com/supranational/blst).
// The purpose of these tests is to detect differences with a different implementation of BLS on the BLS12-381
// curve since the BLS IETF draft (https://datatracker.ietf.org/doc/draft-irtf-cfrg-bls-signature/) doesn't
// provide extensive test vectors.
//
// This file also serves as a way to test the Flow crypto module against random input data
// generated by the "rapid" package. If the comparison against BLST is removed in the future,
// it is mandatory to add fuzzing-like tests using random inputs.
//
// A detected difference with BLST library doesn't necessary mean a bug or a non-standard implementation since
// both libraries might have made different choices. It is nevertheless a good flag for possible bugs or deviations
// from the standard as both libraries are being developed.
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
blst "github.com/supranational/blst/bindings/go"
"pgregory.net/rapid"
)
// validPrivateKeyBytesFlow generates bytes of a valid private key in Flow library
func validPrivateKeyBytesFlow(t *rapid.T) []byte {
seed := rapid.SliceOfN(rapid.Byte(), KeyGenSeedMinLen, KeyGenSeedMaxLen).Draw(t, "seed").([]byte)
sk, err := GeneratePrivateKey(BLSBLS12381, seed)
// TODO: require.NoError(t, err) seems to mess with rapid
if err != nil {
assert.FailNow(t, "failed key generation")
}
return sk.Encode()
}
// validPublicKeyBytesFlow generates bytes of a valid public key in Flow library
func validPublicKeyBytesFlow(t *rapid.T) []byte {
seed := rapid.SliceOfN(rapid.Byte(), KeyGenSeedMinLen, KeyGenSeedMaxLen).Draw(t, "seed").([]byte)
sk, err := GeneratePrivateKey(BLSBLS12381, seed)
require.NoError(t, err)
return sk.PublicKey().Encode()
}
// validSignatureBytesFlow generates bytes of a valid signature in Flow library
func validSignatureBytesFlow(t *rapid.T) []byte {
seed := rapid.SliceOfN(rapid.Byte(), KeyGenSeedMinLen, KeyGenSeedMaxLen).Draw(t, "seed").([]byte)
sk, err := GeneratePrivateKey(BLSBLS12381, seed)
require.NoError(t, err)
hasher := NewExpandMsgXOFKMAC128("random_tag")
message := rapid.SliceOfN(rapid.Byte(), 1, 1000).Draw(t, "msg").([]byte)
signature, err := sk.Sign(message, hasher)
require.NoError(t, err)
return signature
}
// validPrivateKeyBytesBLST generates bytes of a valid private key in BLST library
func validPrivateKeyBytesBLST(t *rapid.T) []byte {
randomSlice := rapid.SliceOfN(rapid.Byte(), KeyGenSeedMinLen, KeyGenSeedMaxLen)
ikm := randomSlice.Draw(t, "ikm").([]byte)
return blst.KeyGen(ikm).Serialize()
}
// validPublicKeyBytesBLST generates bytes of a valid public key in BLST library
func validPublicKeyBytesBLST(t *rapid.T) []byte {
ikm := rapid.SliceOfN(rapid.Byte(), KeyGenSeedMinLen, KeyGenSeedMaxLen).Draw(t, "ikm").([]byte)
blstS := blst.KeyGen(ikm)
blstG2 := new(blst.P2Affine).From(blstS)
return blstG2.Compress()
}
// validSignatureBytesBLST generates bytes of a valid signature in BLST library
func validSignatureBytesBLST(t *rapid.T) []byte {
ikm := rapid.SliceOfN(rapid.Byte(), KeyGenSeedMinLen, KeyGenSeedMaxLen).Draw(t, "ikm").([]byte)
blstS := blst.KeyGen(ikm[:])
blstG1 := new(blst.P1Affine).From(blstS)
return blstG1.Compress()
}
// testEncodeDecodePrivateKeyCrossBLST tests encoding and decoding of private keys are consistent with BLST.
// This test assumes private key serialization is identical to the one in BLST.
func testEncodeDecodePrivateKeyCrossBLST(t *rapid.T) {
randomSlice := rapid.SliceOfN(rapid.Byte(), prKeyLengthBLSBLS12381, prKeyLengthBLSBLS12381)
validSliceFlow := rapid.Custom(validPrivateKeyBytesFlow)
validSliceBLST := rapid.Custom(validPrivateKeyBytesBLST)
// skBytes are bytes of either a valid or a random private key
skBytes := rapid.OneOf(randomSlice, validSliceFlow, validSliceBLST).Example().([]byte)
// check decoding results are consistent
skFlow, err := DecodePrivateKey(BLSBLS12381, skBytes)
var skBLST blst.Scalar
res := skBLST.Deserialize(skBytes)
flowPass := err == nil
blstPass := res != nil
require.Equal(t, flowPass, blstPass, "deserialization of the private key %x differs", skBytes)
// check private keys are equal
if blstPass && flowPass {
skFlowOutBytes := skFlow.Encode()
skBLSTOutBytes := skBLST.Serialize()
assert.Equal(t, skFlowOutBytes, skBLSTOutBytes)
}
}
// testEncodeDecodePublicKeyCrossBLST tests encoding and decoding of public keys keys are consistent with BLST.
// This test assumes public key serialization is identical to the one in BLST.
func testEncodeDecodePublicKeyCrossBLST(t *rapid.T) {
randomSlice := rapid.SliceOfN(rapid.Byte(), PubKeyLenBLSBLS12381, PubKeyLenBLSBLS12381)
validSliceFlow := rapid.Custom(validPublicKeyBytesFlow)
validSliceBLST := rapid.Custom(validPublicKeyBytesBLST)
// pkBytes are bytes of either a valid or a random public key
pkBytes := rapid.OneOf(randomSlice, validSliceFlow, validSliceBLST).Example().([]byte)
// check decoding results are consistent
pkFlow, err := DecodePublicKey(BLSBLS12381, pkBytes)
var pkBLST blst.P2Affine
res := pkBLST.Deserialize(pkBytes)
pkValidBLST := pkBLST.KeyValidate()
flowPass := err == nil
blstPass := res != nil && pkValidBLST
require.Equal(t, flowPass, blstPass, "deserialization of pubkey %x differs", pkBytes)
// check public keys are equal
if flowPass && blstPass {
pkFlowOutBytes := pkFlow.Encode()
pkBLSTOutBytes := pkBLST.Compress()
assert.Equal(t, pkFlowOutBytes, pkBLSTOutBytes)
}
}
// testEncodeDecodeSignatureCrossBLST tests encoding and decoding of signatures are consistent with BLST.
// This test assumes signature serialization is identical to the one in BLST.
func testEncodeDecodeSignatureCrossBLST(t *rapid.T) {
randomSlice := rapid.SliceOfN(rapid.Byte(), SignatureLenBLSBLS12381, SignatureLenBLSBLS12381)
validSignatureFlow := rapid.Custom(validSignatureBytesFlow)
validSignatureBLST := rapid.Custom(validSignatureBytesBLST)
// sigBytes are bytes of either a valid or a random signature
sigBytes := rapid.OneOf(randomSlice, validSignatureFlow, validSignatureBLST).Example().([]byte)
// check decoding results are consistent
var pointFlow pointG1
// here we test readPointG1 rather than the simple Signature type alias
err := readPointG1(&pointFlow, sigBytes)
flowPass := (err == nil) && (checkMembershipG1(&pointFlow) == int(valid))
var pointBLST blst.P1Affine
res := pointBLST.Uncompress(sigBytes)
// flow validation has no infinity rejection for G1
blstPass := (res != nil) && pointBLST.SigValidate(false)
require.Equal(t, flowPass, blstPass, "deserialization of signature %x differs", sigBytes)
// check both signatures (G1 points) are equal
if flowPass && blstPass {
sigFlowOutBytes := make([]byte, signatureLengthBLSBLS12381)
writePointG1(sigFlowOutBytes, &pointFlow)
sigBLSTOutBytes := pointBLST.Compress()
assert.Equal(t, sigFlowOutBytes, sigBLSTOutBytes)
}
}
// testSignHashCrossBLST tests signing a hashed message is consistent with BLST.
//
// The tests assumes the used hash-to-field and map-to-curve are identical in the 2 signatures:
// - hash-to-field : use XMD_SHA256 in both signatures
// - map to curve : Flow and BLST use an SWU mapping consistent with the test vector in
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-14#appendix-J.9.1
// (Flow map to curve is tested agaisnt the IETF draft in TestMapToG1, BLST map to curve is not
// tested in this repo)
//
// The test also assumes Flow signature serialization is identical to the one in BLST.
func testSignHashCrossBLST(t *rapid.T) {
// generate two private keys from the same seed
skBytes := rapid.Custom(validPrivateKeyBytesFlow).Example().([]byte)
skFlow, err := DecodePrivateKey(BLSBLS12381, skBytes)
require.NoError(t, err)
var skBLST blst.Scalar
res := skBLST.Deserialize(skBytes)
require.NotNil(t, res)
// generate two signatures using both libraries
blsCipher := []byte("BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_")
message := rapid.SliceOfN(rapid.Byte(), 1, 1000).Example().([]byte)
var sigBLST blst.P1Affine
sigBLST.Sign(&skBLST, message, blsCipher)
sigBytesBLST := sigBLST.Compress()
skFlowBLS, ok := skFlow.(*prKeyBLSBLS12381)
require.True(t, ok, "incoherent key type assertion")
sigFlow := skFlowBLS.signWithXMDSHA256(message)
sigBytesFlow := sigFlow.Bytes()
// check both signatures are equal
assert.Equal(t, sigBytesBLST, sigBytesFlow)
}
func testKeyGenCrossBLST(t *rapid.T) {
seed := rapid.SliceOfN(rapid.Byte(), KeyGenSeedMinLen, KeyGenSeedMaxLen).Draw(t, "seed").([]byte)
skFlow, err := GeneratePrivateKey(BLSBLS12381, seed)
if err != nil {
assert.FailNow(t, "failed key generation")
}
skBLST := blst.KeyGen(seed)
assert.Equal(t, skFlow.Encode(), skBLST.Serialize())
}
func TestAgainstBLST(t *testing.T) {
rapid.Check(t, testKeyGenCrossBLST)
rapid.Check(t, testEncodeDecodePrivateKeyCrossBLST)
rapid.Check(t, testEncodeDecodePublicKeyCrossBLST)
rapid.Check(t, testEncodeDecodeSignatureCrossBLST)
rapid.Check(t, testSignHashCrossBLST)
}