github.com/onflow/flow-go@v0.33.17/fvm/environment/random_generator.go (about)

     1  package environment
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/onflow/flow-go/crypto/random"
     7  	"github.com/onflow/flow-go/fvm/storage/state"
     8  	"github.com/onflow/flow-go/fvm/tracing"
     9  	"github.com/onflow/flow-go/module/trace"
    10  	"github.com/onflow/flow-go/state/protocol/prg"
    11  )
    12  
    13  // EntropyProvider represents an entropy (source of randomness) provider
    14  type EntropyProvider interface {
    15  	// RandomSource provides a source of entropy that can be
    16  	// expanded into randoms (using a pseudo-random generator).
    17  	// The returned slice should have at least 128 bits of entropy.
    18  	// The function doesn't error in normal operations, any
    19  	// error should be treated as an exception.
    20  	RandomSource() ([]byte, error)
    21  }
    22  
    23  type RandomGenerator interface {
    24  	// ReadRandom reads pseudo-random bytes into the input slice, using distributed randomness.
    25  	// The name follows Cadence interface
    26  	ReadRandom([]byte) error
    27  }
    28  
    29  var _ RandomGenerator = (*randomGenerator)(nil)
    30  
    31  // randomGenerator implements RandomGenerator and is used
    32  // for the transactions execution environment
    33  type randomGenerator struct {
    34  	tracer        tracing.TracerSpan
    35  	entropySource EntropyProvider
    36  	salt          []byte
    37  	prg           random.Rand
    38  	isPRGCreated  bool
    39  }
    40  
    41  type ParseRestrictedRandomGenerator struct {
    42  	txnState state.NestedTransactionPreparer
    43  	impl     RandomGenerator
    44  }
    45  
    46  func NewParseRestrictedRandomGenerator(
    47  	txnState state.NestedTransactionPreparer,
    48  	impl RandomGenerator,
    49  ) RandomGenerator {
    50  	return ParseRestrictedRandomGenerator{
    51  		txnState: txnState,
    52  		impl:     impl,
    53  	}
    54  }
    55  
    56  func (gen ParseRestrictedRandomGenerator) ReadRandom(buf []byte) error {
    57  	return parseRestrict1Arg(
    58  		gen.txnState,
    59  		trace.FVMEnvRandom,
    60  		gen.impl.ReadRandom,
    61  		buf)
    62  }
    63  
    64  func NewRandomGenerator(
    65  	tracer tracing.TracerSpan,
    66  	entropySource EntropyProvider,
    67  	salt []byte,
    68  ) RandomGenerator {
    69  	gen := &randomGenerator{
    70  		tracer:        tracer,
    71  		entropySource: entropySource,
    72  		salt:          salt,
    73  		isPRGCreated:  false, // PRG is not created
    74  	}
    75  
    76  	return gen
    77  }
    78  
    79  func (gen *randomGenerator) createPRG() (random.Rand, error) {
    80  	// Use the protocol state source of randomness [SoR] for the current block's
    81  	// execution
    82  	source, err := gen.entropySource.RandomSource()
    83  	// `RandomSource` does not error in normal operations.
    84  	// Any error should be treated as an exception.
    85  	if err != nil {
    86  		return nil, fmt.Errorf("reading random source from state failed: %w", err)
    87  	}
    88  
    89  	// Use the state/protocol PRG derivation from the source of randomness:
    90  	//  - for the transaction execution case, the PRG used must be a CSPRG
    91  	//  - use the state/protocol/prg customizer defined for the execution environment
    92  	//  - use the salt as an extra diversifier of the CSPRG. Although this
    93  	//    does not add any extra entropy to the output, it allows creating an independent
    94  	//    PRG for each transaction or script.
    95  	csprg, err := prg.New(source, prg.ExecutionEnvironment, gen.salt)
    96  	if err != nil {
    97  		return nil, fmt.Errorf("failed to create a CSPRG from source: %w", err)
    98  	}
    99  
   100  	return csprg, nil
   101  }
   102  
   103  // ReadRandom reads pseudo-random bytes into the input slice using the underlying PRG (currently
   104  // using a crypto-secure one). This function is not thread safe, due to the gen.prg
   105  // instance currently used. This is fine because a
   106  // single transaction has a single RandomGenerator and is run in a single
   107  // thread.
   108  func (gen *randomGenerator) ReadRandom(buf []byte) error {
   109  	defer gen.tracer.StartExtensiveTracingChildSpan(
   110  		trace.FVMEnvRandom).End()
   111  
   112  	// PRG creation is only done once.
   113  	if !gen.isPRGCreated {
   114  		newPRG, err := gen.createPRG()
   115  		if err != nil {
   116  			return err
   117  		}
   118  		gen.prg = newPRG
   119  		gen.isPRGCreated = true
   120  	}
   121  
   122  	gen.prg.Read(buf)
   123  	return nil
   124  }