github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/consensus/hotstuff/signature/randombeacon_inspector_test.go (about)

     1  package signature
     2  
     3  import (
     4  	"errors"
     5  	"math/rand"
     6  	"sync"
     7  	"testing"
     8  
     9  	"github.com/onflow/crypto"
    10  	"github.com/onflow/crypto/hash"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  	"github.com/stretchr/testify/suite"
    14  
    15  	"github.com/onflow/flow-go/consensus/hotstuff/model"
    16  	"github.com/onflow/flow-go/module/signature"
    17  	"github.com/onflow/flow-go/utils/unittest"
    18  )
    19  
    20  func TestRandomBeaconInspector(t *testing.T) {
    21  	suite.Run(t, new(randomBeaconSuite))
    22  }
    23  
    24  type randomBeaconSuite struct {
    25  	suite.Suite
    26  	rng                       *rand.Rand
    27  	n                         int
    28  	threshold                 int
    29  	kmac                      hash.Hasher
    30  	signers                   []int
    31  	thresholdSignatureMessage []byte
    32  	skShares                  []crypto.PrivateKey
    33  	pkShares                  []crypto.PublicKey
    34  	pkGroup                   crypto.PublicKey
    35  }
    36  
    37  func (rs *randomBeaconSuite) SetupTest() {
    38  	rs.n = 10
    39  	rs.threshold = signature.RandomBeaconThreshold(rs.n)
    40  
    41  	// generate threshold keys
    42  	rs.rng = unittest.GetPRG(rs.T())
    43  	seed := make([]byte, crypto.KeyGenSeedMinLen)
    44  	_, err := rs.rng.Read(seed)
    45  	require.NoError(rs.T(), err)
    46  	rs.skShares, rs.pkShares, rs.pkGroup, err = crypto.BLSThresholdKeyGen(rs.n, rs.threshold, seed)
    47  	require.NoError(rs.T(), err)
    48  
    49  	// generate signature shares
    50  	rs.signers = make([]int, 0, rs.n)
    51  
    52  	// hasher
    53  	rs.kmac = signature.NewBLSHasher(signature.RandomBeaconTag)
    54  	rs.thresholdSignatureMessage = []byte("random_message")
    55  
    56  	// fill the signers list and shuffle it
    57  	for i := 0; i < rs.n; i++ {
    58  		rs.signers = append(rs.signers, i)
    59  	}
    60  	rs.rng.Shuffle(rs.n, func(i, j int) {
    61  		rs.signers[i], rs.signers[j] = rs.signers[j], rs.signers[i]
    62  	})
    63  }
    64  
    65  func (rs *randomBeaconSuite) TestHappyPath() {
    66  	follower, err := NewRandomBeaconInspector(rs.pkGroup, rs.pkShares, rs.threshold, rs.thresholdSignatureMessage)
    67  	require.NoError(rs.T(), err)
    68  
    69  	// check EnoughShares
    70  	enough := follower.EnoughShares()
    71  	assert.False(rs.T(), enough)
    72  	var wg sync.WaitGroup
    73  	// create (t) signatures of the first randomly chosen signers
    74  	// (1 signature short of the threshold)
    75  	for j := 0; j < rs.threshold; j++ {
    76  		wg.Add(1)
    77  		// test thread safety
    78  		go func(j int) {
    79  			defer wg.Done()
    80  			i := rs.signers[j]
    81  			share, err := rs.skShares[i].Sign(rs.thresholdSignatureMessage, rs.kmac)
    82  			require.NoError(rs.T(), err)
    83  			// Verify
    84  			err = follower.Verify(i, share)
    85  			assert.NoError(rs.T(), err)
    86  			// TrustedAdd
    87  			enough, err := follower.TrustedAdd(i, share)
    88  			assert.NoError(rs.T(), err)
    89  			assert.False(rs.T(), enough)
    90  			// check EnoughSignature
    91  			assert.False(rs.T(), follower.EnoughShares(), "threshold shouldn't be reached")
    92  		}(j)
    93  	}
    94  	wg.Wait()
    95  	// add the last required signature to get (t+1) shares
    96  	i := rs.signers[rs.threshold]
    97  	share, err := rs.skShares[i].Sign(rs.thresholdSignatureMessage, rs.kmac)
    98  	require.NoError(rs.T(), err)
    99  	err = follower.Verify(i, share)
   100  	assert.NoError(rs.T(), err)
   101  	enough, err = follower.TrustedAdd(i, share)
   102  	assert.NoError(rs.T(), err)
   103  	assert.True(rs.T(), enough)
   104  	// check EnoughSignature
   105  	assert.True(rs.T(), follower.EnoughShares())
   106  
   107  	// add a share when threshold is reached
   108  	if rs.threshold+1 < rs.n {
   109  		i := rs.signers[rs.threshold+1]
   110  		share, err := rs.skShares[i].Sign(rs.thresholdSignatureMessage, rs.kmac)
   111  		require.NoError(rs.T(), err)
   112  		// Trusted Add
   113  		enough, err := follower.TrustedAdd(i, share)
   114  		assert.NoError(rs.T(), err)
   115  		assert.True(rs.T(), enough)
   116  	}
   117  	// reconstruct the threshold signature
   118  	thresholdsignature, err := follower.Reconstruct()
   119  	require.NoError(rs.T(), err)
   120  	// VerifyThresholdSignature
   121  	verif, err := rs.pkGroup.Verify(thresholdsignature, rs.thresholdSignatureMessage, rs.kmac)
   122  	require.NoError(rs.T(), err)
   123  	assert.True(rs.T(), verif)
   124  }
   125  
   126  func (rs *randomBeaconSuite) TestDuplicateSigner() {
   127  	follower, err := NewRandomBeaconInspector(rs.pkGroup, rs.pkShares, rs.threshold, rs.thresholdSignatureMessage)
   128  	require.NoError(rs.T(), err)
   129  
   130  	// Create a share and add it
   131  	i := 0
   132  	share, err := rs.skShares[i].Sign(rs.thresholdSignatureMessage, rs.kmac)
   133  	require.NoError(rs.T(), err)
   134  	enough, err := follower.TrustedAdd(i, share)
   135  	assert.NoError(rs.T(), err)
   136  	assert.False(rs.T(), enough)
   137  
   138  	// Add an existing share
   139  	// TrustedAdd
   140  	enough, err = follower.TrustedAdd(i, share)
   141  	assert.Error(rs.T(), err)
   142  	assert.True(rs.T(), model.IsDuplicatedSignerError(err))
   143  	assert.False(rs.T(), enough)
   144  }
   145  
   146  func (rs *randomBeaconSuite) TestInvalidSignerIndex() {
   147  	follower, err := NewRandomBeaconInspector(rs.pkGroup, rs.pkShares, rs.threshold, rs.thresholdSignatureMessage)
   148  	require.NoError(rs.T(), err)
   149  
   150  	share, err := rs.skShares[0].Sign(rs.thresholdSignatureMessage, rs.kmac)
   151  	require.NoError(rs.T(), err)
   152  	// invalid index
   153  	for _, invalidIndex := range []int{len(rs.pkShares) + 1, -1} {
   154  		// Verify
   155  		err = follower.Verify(invalidIndex, share)
   156  		assert.Error(rs.T(), err)
   157  		assert.True(rs.T(), model.IsInvalidSignerError(err))
   158  		// TrustedAdd
   159  		enough, err := follower.TrustedAdd(invalidIndex, share)
   160  		assert.Error(rs.T(), err)
   161  		assert.True(rs.T(), model.IsInvalidSignerError(err))
   162  		assert.False(rs.T(), enough)
   163  	}
   164  }
   165  
   166  func (rs *randomBeaconSuite) TestInvalidSignature() {
   167  	follower, err := NewRandomBeaconInspector(rs.pkGroup, rs.pkShares, rs.threshold, rs.thresholdSignatureMessage)
   168  	require.NoError(rs.T(), err)
   169  	index := rs.rng.Intn(rs.n) // random signer
   170  	share, err := rs.skShares[index].Sign(rs.thresholdSignatureMessage, rs.kmac)
   171  	require.NoError(rs.T(), err)
   172  
   173  	// alter signature - signature is rs.not a valid point
   174  	share[4] ^= 1
   175  	// Verify
   176  	err = follower.Verify(index, share)
   177  	assert.Error(rs.T(), err)
   178  	assert.True(rs.T(), errors.Is(err, model.ErrInvalidSignature))
   179  	// restore share
   180  	share[4] ^= 1
   181  
   182  	// valid curve point but invalid signature
   183  	otherIndex := (index + 1) % len(rs.pkShares) // otherIndex is different than index
   184  	// VerifyShare
   185  	err = follower.Verify(otherIndex, share)
   186  	assert.Error(rs.T(), err)
   187  	assert.True(rs.T(), errors.Is(err, model.ErrInvalidSignature))
   188  }
   189  
   190  func (rs *randomBeaconSuite) TestConstructorErrors() {
   191  	// too few key shares
   192  	pkSharesInvalid := make([]crypto.PublicKey, crypto.ThresholdSignMinSize-1)
   193  	i, err := NewRandomBeaconInspector(rs.pkGroup, pkSharesInvalid, rs.threshold, rs.thresholdSignatureMessage)
   194  	assert.True(rs.T(), model.IsConfigurationError(err))
   195  	assert.Nil(rs.T(), i)
   196  
   197  	// too many key shares
   198  	pkSharesInvalid = make([]crypto.PublicKey, crypto.ThresholdSignMaxSize+1)
   199  	i, err = NewRandomBeaconInspector(rs.pkGroup, pkSharesInvalid, rs.threshold, rs.thresholdSignatureMessage)
   200  	assert.True(rs.T(), model.IsConfigurationError(err))
   201  	assert.Nil(rs.T(), i)
   202  
   203  	// threshold too large
   204  	i, err = NewRandomBeaconInspector(rs.pkGroup, rs.pkShares, len(rs.pkShares), rs.thresholdSignatureMessage)
   205  	assert.True(rs.T(), model.IsConfigurationError(err))
   206  	assert.Nil(rs.T(), i)
   207  
   208  	// threshold negative
   209  	i, err = NewRandomBeaconInspector(rs.pkGroup, rs.pkShares, 0, rs.thresholdSignatureMessage)
   210  	assert.True(rs.T(), model.IsConfigurationError(err))
   211  	assert.Nil(rs.T(), i)
   212  
   213  	// included non-BLS key in public key shares
   214  	pkSharesInvalid = append(([]crypto.PublicKey)(nil), rs.pkShares...) // copy
   215  	pkSharesInvalid[len(pkSharesInvalid)-1] = unittest.KeyFixture(crypto.ECDSAP256).PublicKey()
   216  	i, err = NewRandomBeaconInspector(rs.pkGroup, pkSharesInvalid, rs.threshold, rs.thresholdSignatureMessage)
   217  	assert.True(rs.T(), model.IsConfigurationError(err))
   218  	assert.Nil(rs.T(), i)
   219  }