github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/fvm/crypto/crypto_test.go (about)

     1  package crypto_test
     2  
     3  import (
     4  	"crypto/rand"
     5  	"fmt"
     6  	"testing"
     7  	"unicode/utf8"
     8  
     9  	"github.com/fxamacker/cbor/v2"
    10  	"github.com/onflow/cadence/runtime"
    11  	onflowCrypto "github.com/onflow/crypto"
    12  	"github.com/onflow/crypto/hash"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  
    16  	"github.com/onflow/flow-go/fvm/crypto"
    17  	"github.com/onflow/flow-go/fvm/errors"
    18  	"github.com/onflow/flow-go/model/flow"
    19  	msig "github.com/onflow/flow-go/module/signature"
    20  )
    21  
    22  func TestHashWithTag(t *testing.T) {
    23  	t.Run("tag too long", func(t *testing.T) {
    24  		algorithms := []hash.HashingAlgorithm{
    25  			hash.SHA2_256,
    26  			hash.SHA2_384,
    27  			hash.SHA3_256,
    28  			hash.SHA3_384,
    29  			hash.Keccak_256,
    30  		}
    31  
    32  		okTag := [flow.DomainTagLength / 2]byte{}   // tag does not exceed 32 bytes
    33  		longTag := [flow.DomainTagLength + 1]byte{} // tag larger that 32 bytes
    34  
    35  		for i, algorithm := range algorithms {
    36  			t.Run(fmt.Sprintf("algo %d: %v", i, algorithm), func(t *testing.T) {
    37  				_, err := crypto.HashWithTag(algorithm, string(longTag[:]), []byte("some data"))
    38  				require.Error(t, err)
    39  			})
    40  
    41  			t.Run(fmt.Sprintf("algo %d: %v - control (tag ok)", i, algorithm), func(t *testing.T) {
    42  				_, err := crypto.HashWithTag(algorithm, string(okTag[:]), []byte("some data"))
    43  				require.NoError(t, err)
    44  			})
    45  		}
    46  	})
    47  }
    48  
    49  func TestVerifySignatureFromRuntime(t *testing.T) {
    50  
    51  	// make sure the seed length is larger than miniumum seed lengths of all signature schemes
    52  	seedLength := 64
    53  
    54  	correctCombinations := map[runtime.SignatureAlgorithm]map[runtime.HashAlgorithm]struct{}{
    55  
    56  		runtime.SignatureAlgorithmBLS_BLS12_381: {
    57  			runtime.HashAlgorithmKMAC128_BLS_BLS12_381: {},
    58  		},
    59  		runtime.SignatureAlgorithmECDSA_P256: {
    60  			runtime.HashAlgorithmSHA2_256:   {},
    61  			runtime.HashAlgorithmSHA3_256:   {},
    62  			runtime.HashAlgorithmKECCAK_256: {},
    63  		},
    64  		runtime.SignatureAlgorithmECDSA_secp256k1: {
    65  			runtime.HashAlgorithmSHA2_256:   {},
    66  			runtime.HashAlgorithmSHA3_256:   {},
    67  			runtime.HashAlgorithmKECCAK_256: {},
    68  		},
    69  	}
    70  
    71  	t.Run("verify should fail on incorrect combinations", func(t *testing.T) {
    72  
    73  		signatureAlgos := []runtime.SignatureAlgorithm{
    74  			runtime.SignatureAlgorithmECDSA_P256,
    75  			runtime.SignatureAlgorithmECDSA_secp256k1,
    76  			runtime.SignatureAlgorithmBLS_BLS12_381,
    77  		}
    78  		hashAlgos := []runtime.HashAlgorithm{
    79  			runtime.HashAlgorithmSHA2_256,
    80  			runtime.HashAlgorithmSHA2_384,
    81  			runtime.HashAlgorithmSHA3_256,
    82  			runtime.HashAlgorithmSHA3_384,
    83  			runtime.HashAlgorithmKMAC128_BLS_BLS12_381,
    84  			runtime.HashAlgorithmKECCAK_256,
    85  		}
    86  
    87  		for _, s := range signatureAlgos {
    88  			for _, h := range hashAlgos {
    89  				t.Run(fmt.Sprintf("combination: %v, %v", s, h), func(t *testing.T) {
    90  					seed := make([]byte, seedLength)
    91  					_, err := rand.Read(seed)
    92  					require.NoError(t, err)
    93  					pk, err := onflowCrypto.GeneratePrivateKey(crypto.RuntimeToCryptoSigningAlgorithm(s), seed)
    94  					require.NoError(t, err)
    95  
    96  					tag := "random_tag"
    97  					var hasher hash.Hasher
    98  					if h != runtime.HashAlgorithmKMAC128_BLS_BLS12_381 {
    99  						hasher, err = crypto.NewPrefixedHashing(crypto.RuntimeToCryptoHashingAlgorithm(h), tag)
   100  						require.NoError(t, err)
   101  					} else {
   102  						hasher = msig.NewBLSHasher(tag)
   103  					}
   104  
   105  					signature := make([]byte, 0)
   106  					sig, err := pk.Sign([]byte("some data"), hasher)
   107  					if _, shouldBeOk := correctCombinations[s][h]; shouldBeOk {
   108  						require.NoError(t, err)
   109  					}
   110  
   111  					if sig != nil {
   112  						signature = sig.Bytes()
   113  					}
   114  
   115  					ok, err := crypto.VerifySignatureFromRuntime(
   116  						signature,
   117  						tag,
   118  						[]byte("some data"),
   119  						pk.PublicKey().Encode(),
   120  						s,
   121  						h,
   122  					)
   123  
   124  					if _, shouldBeOk := correctCombinations[s][h]; shouldBeOk {
   125  						require.NoError(t, err)
   126  						require.True(t, ok)
   127  					} else {
   128  						require.Error(t, err)
   129  						require.False(t, ok)
   130  					}
   131  				})
   132  			}
   133  		}
   134  	})
   135  
   136  	t.Run("BLS tag combinations", func(t *testing.T) {
   137  		cases := []struct {
   138  			signTag   string
   139  			verifyTag string
   140  			require   func(t *testing.T, sigOk bool, err error)
   141  		}{
   142  			{
   143  				signTag:   "random_tag",
   144  				verifyTag: "random_tag",
   145  				require: func(t *testing.T, sigOk bool, err error) {
   146  					require.NoError(t, err)
   147  					require.True(t, sigOk)
   148  				},
   149  			},
   150  			{
   151  				signTag:   "",
   152  				verifyTag: "",
   153  				require: func(t *testing.T, sigOk bool, err error) {
   154  					require.NoError(t, err)
   155  					require.True(t, sigOk)
   156  				},
   157  			}, {
   158  				signTag:   "padding test",
   159  				verifyTag: "padding test" + string([]byte{0, 0, 0, 0, 0}),
   160  				require: func(t *testing.T, sigOk bool, err error) {
   161  					require.NoError(t, err)
   162  					require.False(t, sigOk)
   163  				},
   164  			}, {
   165  				signTag:   "valid tag",
   166  				verifyTag: "different valid tag",
   167  				require: func(t *testing.T, sigOk bool, err error) {
   168  					require.NoError(t, err)
   169  					require.False(t, sigOk)
   170  				},
   171  			}, {
   172  				signTag:   "a very large tag with more than thirty two bytes",
   173  				verifyTag: "a very large tag with more than thirty two bytes",
   174  				require: func(t *testing.T, sigOk bool, err error) {
   175  					require.NoError(t, err)
   176  					require.True(t, sigOk)
   177  				},
   178  			},
   179  		}
   180  
   181  		for _, c := range cases {
   182  			seed := make([]byte, seedLength)
   183  			_, err := rand.Read(seed)
   184  			require.NoError(t, err)
   185  			pk, err := onflowCrypto.GeneratePrivateKey(onflowCrypto.BLSBLS12381, seed)
   186  			require.NoError(t, err)
   187  
   188  			hasher := msig.NewBLSHasher(string(c.signTag))
   189  			signature := make([]byte, 0)
   190  			sig, err := pk.Sign([]byte("some data"), hasher)
   191  			require.NoError(t, err)
   192  
   193  			if sig != nil {
   194  				signature = sig.Bytes()
   195  			}
   196  
   197  			ok, err := crypto.VerifySignatureFromRuntime(
   198  				signature,
   199  				string(c.verifyTag),
   200  				[]byte("some data"),
   201  				pk.PublicKey().Encode(),
   202  				runtime.SignatureAlgorithmBLS_BLS12_381,
   203  				runtime.HashAlgorithmKMAC128_BLS_BLS12_381,
   204  			)
   205  
   206  			c.require(t, ok, err)
   207  		}
   208  	})
   209  
   210  	t.Run("ECDSA tag combinations", func(t *testing.T) {
   211  
   212  		cases := []struct {
   213  			signTag   string
   214  			verifyTag string
   215  			require   func(t *testing.T, sigOk bool, err error)
   216  		}{
   217  			{
   218  				signTag:   "random_tag",
   219  				verifyTag: "random_tag",
   220  				require: func(t *testing.T, sigOk bool, err error) {
   221  					require.NoError(t, err)
   222  					require.True(t, sigOk)
   223  				},
   224  			},
   225  			{
   226  				signTag:   "",
   227  				verifyTag: "",
   228  				require: func(t *testing.T, sigOk bool, err error) {
   229  					require.NoError(t, err)
   230  					require.True(t, sigOk)
   231  				},
   232  			}, {
   233  				signTag:   "padding test",
   234  				verifyTag: "padding test" + string([]byte{0, 0, 0, 0, 0}),
   235  				require: func(t *testing.T, sigOk bool, err error) {
   236  					require.NoError(t, err)
   237  					require.True(t, sigOk)
   238  				},
   239  			}, {
   240  				signTag:   "valid tag",
   241  				verifyTag: "different valid tag",
   242  				require: func(t *testing.T, sigOk bool, err error) {
   243  					require.NoError(t, err)
   244  					require.False(t, sigOk)
   245  				},
   246  			}, {
   247  				signTag:   "valid tag",
   248  				verifyTag: "a very large tag with more than thirty two bytes",
   249  				require: func(t *testing.T, sigOk bool, err error) {
   250  					require.Error(t, err)
   251  					require.False(t, sigOk)
   252  				},
   253  			},
   254  		}
   255  
   256  		for _, c := range cases {
   257  			for s, hMaps := range correctCombinations {
   258  				if s == runtime.SignatureAlgorithmBLS_BLS12_381 {
   259  					// skip BLS to only cover ECDSA in this test
   260  					continue
   261  				}
   262  				for h := range hMaps {
   263  					t.Run(fmt.Sprintf("hash tag: %v, verify tag: %v [%v, %v]", c.signTag, c.verifyTag, s, h), func(t *testing.T) {
   264  
   265  						seed := make([]byte, seedLength)
   266  						_, err := rand.Read(seed)
   267  						require.NoError(t, err)
   268  						pk, err := onflowCrypto.GeneratePrivateKey(crypto.RuntimeToCryptoSigningAlgorithm(s), seed)
   269  						require.NoError(t, err)
   270  
   271  						hasher, err := crypto.NewPrefixedHashing(crypto.RuntimeToCryptoHashingAlgorithm(h), c.signTag)
   272  						require.NoError(t, err)
   273  
   274  						data := []byte("some data")
   275  						sig, err := pk.Sign(data, hasher)
   276  						require.NoError(t, err)
   277  						signature := sig.Bytes()
   278  
   279  						ok, err := crypto.VerifySignatureFromRuntime(
   280  							signature,
   281  							c.verifyTag,
   282  							data,
   283  							pk.PublicKey().Encode(),
   284  							s,
   285  							h,
   286  						)
   287  
   288  						c.require(t, ok, err)
   289  					})
   290  				}
   291  			}
   292  		}
   293  	})
   294  }
   295  
   296  func TestVerifySignatureFromTransaction(t *testing.T) {
   297  
   298  	// make sure the seed length is larger than miniumum seed lengths of all signature schemes
   299  	seedLength := 64
   300  
   301  	correctCombinations := map[onflowCrypto.SigningAlgorithm]map[hash.HashingAlgorithm]struct{}{
   302  		onflowCrypto.ECDSAP256: {
   303  			hash.SHA2_256: {},
   304  			hash.SHA3_256: {},
   305  		},
   306  		onflowCrypto.ECDSASecp256k1: {
   307  			hash.SHA2_256: {},
   308  			hash.SHA3_256: {},
   309  		},
   310  	}
   311  
   312  	t.Run("verify should fail on incorrect combinations", func(t *testing.T) {
   313  
   314  		signatureAlgos := []onflowCrypto.SigningAlgorithm{
   315  			onflowCrypto.ECDSAP256,
   316  			onflowCrypto.ECDSASecp256k1,
   317  			onflowCrypto.BLSBLS12381,
   318  		}
   319  		hashAlgos := []hash.HashingAlgorithm{
   320  			hash.SHA2_256,
   321  			hash.SHA2_384,
   322  			hash.SHA3_256,
   323  			hash.SHA3_384,
   324  			hash.KMAC128,
   325  			hash.Keccak_256,
   326  		}
   327  
   328  		for _, s := range signatureAlgos {
   329  			for _, h := range hashAlgos {
   330  				t.Run(fmt.Sprintf("combination: %v, %v", s, h), func(t *testing.T) {
   331  					seed := make([]byte, seedLength)
   332  					_, err := rand.Read(seed)
   333  					require.NoError(t, err)
   334  					sk, err := onflowCrypto.GeneratePrivateKey(s, seed)
   335  					require.NoError(t, err)
   336  
   337  					tag := string(flow.TransactionDomainTag[:])
   338  					var hasher hash.Hasher
   339  					if h != hash.KMAC128 {
   340  						hasher, err = crypto.NewPrefixedHashing(h, tag)
   341  						require.NoError(t, err)
   342  					} else {
   343  						hasher = msig.NewBLSHasher(tag)
   344  					}
   345  
   346  					signature := make([]byte, 0)
   347  					data := []byte("some_data")
   348  					sig, err := sk.Sign(data, hasher)
   349  					if _, shouldBeOk := correctCombinations[s][h]; shouldBeOk {
   350  						require.NoError(t, err)
   351  					}
   352  
   353  					if sig != nil {
   354  						signature = sig.Bytes()
   355  					}
   356  
   357  					ok, err := crypto.VerifySignatureFromTransaction(signature, data, sk.PublicKey(), h)
   358  
   359  					if _, shouldBeOk := correctCombinations[s][h]; shouldBeOk {
   360  						require.NoError(t, err)
   361  						require.True(t, ok)
   362  					} else {
   363  						require.Error(t, err)
   364  						require.False(t, ok)
   365  					}
   366  				})
   367  			}
   368  		}
   369  	})
   370  
   371  	t.Run("tag combinations", func(t *testing.T) {
   372  
   373  		cases := []struct {
   374  			signTag string
   375  			require func(t *testing.T, sigOk bool, err error)
   376  		}{
   377  			{
   378  				signTag: string(flow.TransactionDomainTag[:]),
   379  				require: func(t *testing.T, sigOk bool, err error) {
   380  					require.NoError(t, err)
   381  					require.True(t, sigOk)
   382  				},
   383  			},
   384  			{
   385  				signTag: "",
   386  				require: func(t *testing.T, sigOk bool, err error) {
   387  					require.NoError(t, err)
   388  					require.False(t, sigOk)
   389  				},
   390  			}, {
   391  				signTag: "random_tag",
   392  				require: func(t *testing.T, sigOk bool, err error) {
   393  					require.NoError(t, err)
   394  					require.False(t, sigOk)
   395  				},
   396  			},
   397  		}
   398  
   399  		for _, c := range cases {
   400  			for s, hMaps := range correctCombinations {
   401  				for h := range hMaps {
   402  					t.Run(fmt.Sprintf("sign tag: %v [%v, %v]", c.signTag, s, h), func(t *testing.T) {
   403  						seed := make([]byte, seedLength)
   404  						_, err := rand.Read(seed)
   405  						require.NoError(t, err)
   406  						sk, err := onflowCrypto.GeneratePrivateKey(s, seed)
   407  						require.NoError(t, err)
   408  
   409  						hasher, err := crypto.NewPrefixedHashing(h, c.signTag)
   410  						require.NoError(t, err)
   411  
   412  						data := []byte("some data")
   413  						sig, err := sk.Sign(data, hasher)
   414  						require.NoError(t, err)
   415  						signature := sig.Bytes()
   416  
   417  						ok, err := crypto.VerifySignatureFromTransaction(signature, data, sk.PublicKey(), h)
   418  						c.require(t, ok, err)
   419  					})
   420  				}
   421  			}
   422  		}
   423  	})
   424  }
   425  
   426  func TestValidatePublicKey(t *testing.T) {
   427  
   428  	validPublicKey := func(t *testing.T, s runtime.SignatureAlgorithm) []byte {
   429  		seed := make([]byte, onflowCrypto.KeyGenSeedMinLen)
   430  		_, err := rand.Read(seed)
   431  		require.NoError(t, err)
   432  		sk, err := onflowCrypto.GeneratePrivateKey(crypto.RuntimeToCryptoSigningAlgorithm(s), seed)
   433  		require.NoError(t, err)
   434  		return sk.PublicKey().Encode()
   435  	}
   436  
   437  	t.Run("Unknown algorithm should return false", func(t *testing.T) {
   438  		err := crypto.ValidatePublicKey(runtime.SignatureAlgorithmUnknown, validPublicKey(t, runtime.SignatureAlgorithmECDSA_P256))
   439  		require.Error(t, err)
   440  	})
   441  
   442  	t.Run("valid public key should return true", func(t *testing.T) {
   443  		signatureAlgos := []runtime.SignatureAlgorithm{
   444  			runtime.SignatureAlgorithmECDSA_P256,
   445  			runtime.SignatureAlgorithmECDSA_secp256k1,
   446  			runtime.SignatureAlgorithmBLS_BLS12_381,
   447  		}
   448  		for i, s := range signatureAlgos {
   449  			t.Run(fmt.Sprintf("case %v: %v", i, s), func(t *testing.T) {
   450  				err := crypto.ValidatePublicKey(s, validPublicKey(t, s))
   451  				require.NoError(t, err)
   452  			})
   453  		}
   454  	})
   455  
   456  	t.Run("invalid public key should return false", func(t *testing.T) {
   457  		signatureAlgos := []runtime.SignatureAlgorithm{
   458  			runtime.SignatureAlgorithmECDSA_P256,
   459  			runtime.SignatureAlgorithmECDSA_secp256k1,
   460  			runtime.SignatureAlgorithmBLS_BLS12_381,
   461  		}
   462  		for i, s := range signatureAlgos {
   463  
   464  			t.Run(fmt.Sprintf("case %v: %v", i, s), func(t *testing.T) {
   465  				key := validPublicKey(t, s)
   466  				// This may cause flakiness depending on the public key
   467  				// deserialization scheme used!!
   468  				key[0] ^= 1 // alter one bit of the valid key
   469  				err := crypto.ValidatePublicKey(s, key)
   470  				require.Errorf(t, err, "key is %#x", key)
   471  			})
   472  		}
   473  	})
   474  }
   475  
   476  func TestHashingAlgorithmConversion(t *testing.T) {
   477  	hashingAlgoMapping := map[runtime.HashAlgorithm]hash.HashingAlgorithm{
   478  		runtime.HashAlgorithmSHA2_256:              hash.SHA2_256,
   479  		runtime.HashAlgorithmSHA3_256:              hash.SHA3_256,
   480  		runtime.HashAlgorithmSHA2_384:              hash.SHA2_384,
   481  		runtime.HashAlgorithmSHA3_384:              hash.SHA3_384,
   482  		runtime.HashAlgorithmKMAC128_BLS_BLS12_381: hash.KMAC128,
   483  		runtime.HashAlgorithmKECCAK_256:            hash.Keccak_256,
   484  	}
   485  
   486  	for runtimeAlgo, cryptoAlgo := range hashingAlgoMapping {
   487  		assert.Equal(t, cryptoAlgo, crypto.RuntimeToCryptoHashingAlgorithm(runtimeAlgo))
   488  		assert.Equal(t, runtimeAlgo, crypto.CryptoToRuntimeHashingAlgorithm(cryptoAlgo))
   489  	}
   490  }
   491  
   492  func TestSigningAlgorithmConversion(t *testing.T) {
   493  	signingAlgoMapping := map[runtime.SignatureAlgorithm]onflowCrypto.SigningAlgorithm{
   494  		runtime.SignatureAlgorithmECDSA_P256:      onflowCrypto.ECDSAP256,
   495  		runtime.SignatureAlgorithmECDSA_secp256k1: onflowCrypto.ECDSASecp256k1,
   496  		runtime.SignatureAlgorithmBLS_BLS12_381:   onflowCrypto.BLSBLS12381,
   497  	}
   498  
   499  	for runtimeAlgo, cryptoAlgo := range signingAlgoMapping {
   500  		assert.Equal(t, cryptoAlgo, crypto.RuntimeToCryptoSigningAlgorithm(runtimeAlgo))
   501  		assert.Equal(t, runtimeAlgo, crypto.CryptoToRuntimeSigningAlgorithm(cryptoAlgo))
   502  	}
   503  }
   504  
   505  func TestVerifySignatureFromRuntime_error_handling_produces_valid_utf8_for_invalid_sign_algo(t *testing.T) {
   506  
   507  	invalidSignatureAlgo := runtime.SignatureAlgorithm(164)
   508  
   509  	_, err := crypto.VerifySignatureFromRuntime(
   510  		nil, "", nil, nil, invalidSignatureAlgo, 0,
   511  	)
   512  
   513  	require.True(t, errors.IsValueError(err))
   514  
   515  	require.Contains(t, err.Error(), fmt.Sprintf("%d", invalidSignatureAlgo))
   516  
   517  	errorString := err.Error()
   518  	assert.True(t, utf8.ValidString(errorString))
   519  
   520  	// check if they can encoded and decoded using CBOR
   521  	marshalledBytes, err := cbor.Marshal(errorString)
   522  	require.NoError(t, err)
   523  
   524  	var unmarshalledString string
   525  
   526  	err = cbor.Unmarshal(marshalledBytes, &unmarshalledString)
   527  	require.NoError(t, err)
   528  
   529  	require.Equal(t, errorString, unmarshalledString)
   530  }
   531  
   532  func TestVerifySignatureFromRuntime_error_handling_produces_valid_utf8_for_invalid_hash_algo(t *testing.T) {
   533  
   534  	invalidHashAlgo := runtime.HashAlgorithm(164)
   535  
   536  	_, err := crypto.VerifySignatureFromRuntime(
   537  		nil, "", nil, nil, runtime.SignatureAlgorithmECDSA_P256, invalidHashAlgo,
   538  	)
   539  
   540  	require.True(t, errors.IsValueError(err))
   541  
   542  	require.Contains(t, err.Error(), fmt.Sprintf("%d", invalidHashAlgo))
   543  
   544  	errorString := err.Error()
   545  	assert.True(t, utf8.ValidString(errorString))
   546  
   547  	// check if they can encoded and decoded using CBOR
   548  	marshalledBytes, err := cbor.Marshal(errorString)
   549  	require.NoError(t, err)
   550  
   551  	var unmarshalledString string
   552  
   553  	err = cbor.Unmarshal(marshalledBytes, &unmarshalledString)
   554  	require.NoError(t, err)
   555  
   556  	require.Equal(t, errorString, unmarshalledString)
   557  }
   558  
   559  func TestVerifySignatureFromRuntime_error_handling_produces_valid_utf8_for_invalid_public_key(t *testing.T) {
   560  
   561  	invalidPublicKey := []byte{0xc3, 0x28} // some invalid UTF8
   562  
   563  	_, err := crypto.VerifySignatureFromRuntime(
   564  		nil, "random_tag", nil, invalidPublicKey, runtime.SignatureAlgorithmECDSA_P256, runtime.HashAlgorithmSHA2_256,
   565  	)
   566  
   567  	require.True(t, errors.IsValueError(err))
   568  	errorString := err.Error()
   569  
   570  	require.Contains(t, errorString, fmt.Sprintf("%x", invalidPublicKey))
   571  
   572  	assert.True(t, utf8.ValidString(errorString))
   573  
   574  	// check if they can encoded and decoded using CBOR
   575  	marshalledBytes, err := cbor.Marshal(errorString)
   576  	require.NoError(t, err)
   577  
   578  	var unmarshalledString string
   579  
   580  	err = cbor.Unmarshal(marshalledBytes, &unmarshalledString)
   581  	require.NoError(t, err)
   582  
   583  	require.Equal(t, errorString, unmarshalledString)
   584  }