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