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  }