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 }