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  }