github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/state/protocol/prg/prg.go (about) 1 package prg 2 3 import ( 4 "fmt" 5 6 "golang.org/x/crypto/sha3" 7 8 "github.com/onflow/crypto" 9 "github.com/onflow/crypto/random" 10 ) 11 12 const RandomSourceLength = crypto.SignatureLenBLSBLS12381 13 14 // New returns a PRG seeded by the input source of randomness [SoR]. 15 // The customizer is used to generate a task-specific PRG. A customizer can be any slice 16 // of 12 bytes or less. 17 // The diversifier is used to further diversify the PRGs beyond the customizer. A diversifier 18 // can be a slice of any length. If no diversification is needed, `diversifier` can be `nil`. 19 // 20 // The function uses an extendable-output function (xof) to extract and expand the the input source, 21 // so that any source with enough entropy (at least 128 bits) can be used (no need to pre-hash). 22 // Current implementation generates a ChaCha20-based CSPRG. 23 // 24 // How to use the function in Flow protocol: any sub-protocol that requires deterministic and 25 // distributed randomness should rely on the Flow native randomness provided by the Random Beacon. 26 // The beacon SoR for block B is part of the QC certifying B and can be extracted using the 27 // function `consensus/hotstuff/model.BeaconSignature(*flow.QuorumCertificate)`. It can also be 28 // extracted using the `state/protocol/snapshot.RandomSource()` function. 29 // 30 // While the output is a distributed source of randomness, it should _not_ be used as random 31 // numbers itself. Instead, please use the function `New` to instantiate a PRG, 32 // for deterministic generation of random numbers or permutations (check the random.Rand interface). 33 // 34 // Every Flow sub-protocol should use its own customizer to create an independent PRG. Use the list in 35 // "customizers.go" to add new values. The same sub-protocol can further create independent PRGs 36 // by using `diversifier`. 37 func New(source []byte, customizer []byte, diversifier []byte) (random.Rand, error) { 38 seed, err := xof(source, diversifier, random.Chacha20SeedLen) 39 if err != nil { 40 return nil, fmt.Errorf("extendable output function failed: %w", err) 41 } 42 43 // create random number generator from the seed and customizer 44 rng, err := random.NewChacha20PRG(seed, customizer) 45 if err != nil { 46 return nil, fmt.Errorf("could not create ChaCha20 PRG: %w", err) 47 } 48 return rng, nil 49 } 50 51 // XOF (extendable output function) extracts and expands the input's entropy into 52 // an output byte-slice of length `outLen`. 53 // It also takes a `diversifier` slice as an input to create independent outputs. 54 // 55 // Purpose of this function: it abstracts the extraction and expansion of 56 // entropy from the rest of PRG logic. The source doesn't necessarily have a uniformly 57 // distributed entropy (for instance a cryptographic signature), and hashing doesn't necessarily 58 // output the number of bytes required for the PRG seed (the code currently relies on ChaCha20 but this 59 // choice could evolve). 60 func xof(source []byte, diversifier []byte, outLen int) ([]byte, error) { 61 // CShake is used in this case but any other primitive that acts as a xof 62 // and accepts a diversifier can be used. 63 shake := sha3.NewCShake128(nil, diversifier) 64 _, _ = shake.Write(source) // cshake Write doesn't error 65 out := make([]byte, outLen) 66 _, _ = shake.Read(out) // cshake Read doesn't error 67 return out, nil 68 }