github.com/3andne/restls-client-go@v0.1.6/u_prng.go (about) 1 /* 2 * Copyright (c) 2019, Psiphon Inc. 3 * All rights reserved. 4 * 5 * Released under utls licence: 6 * https://github.com/refraction-networking/utls/blob/master/LICENSE 7 */ 8 9 // This code is a pared down version of: 10 // https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/158caea562287284cc3fa5fcd1b3c97b1addf659/psiphon/common/prng/prng.go 11 12 package tls 13 14 import ( 15 crypto_rand "crypto/rand" 16 "encoding/binary" 17 "io" 18 "math" 19 "math/rand" 20 "sync" 21 22 "golang.org/x/crypto/hkdf" 23 "golang.org/x/crypto/sha3" 24 ) 25 26 const ( 27 PRNGSeedLength = 32 28 ) 29 30 // PRNGSeed is a PRNG seed. 31 type PRNGSeed [PRNGSeedLength]byte 32 33 // NewPRNGSeed creates a new PRNG seed using crypto/rand.Read. 34 func NewPRNGSeed() (*PRNGSeed, error) { 35 seed := new(PRNGSeed) 36 _, err := crypto_rand.Read(seed[:]) 37 if err != nil { 38 return nil, err 39 } 40 return seed, nil 41 } 42 43 // newSaltedPRNGSeed creates a new seed derived from an existing seed and a 44 // salt. A HKDF is applied to the seed and salt. 45 // 46 // newSaltedPRNGSeed is intended for use cases where a single seed needs to be 47 // used in distinct contexts to produce independent random streams. 48 func newSaltedPRNGSeed(seed *PRNGSeed, salt string) (*PRNGSeed, error) { 49 saltedSeed := new(PRNGSeed) 50 _, err := io.ReadFull( 51 hkdf.New(sha3.New256, seed[:], []byte(salt), nil), saltedSeed[:]) 52 if err != nil { 53 return nil, err 54 } 55 return saltedSeed, nil 56 } 57 58 // prng is a seeded, unbiased PRNG based on SHAKE256. that is suitable for use 59 // cases such as obfuscation. Seeding is based on crypto/rand.Read. 60 // 61 // This PRNG is _not_ for security use cases including production cryptographic 62 // key generation. 63 // 64 // It is safe to make concurrent calls to a PRNG instance. 65 // 66 // PRNG conforms to io.Reader and math/rand.Source, with additional helper 67 // functions. 68 type prng struct { 69 rand *rand.Rand 70 randomStreamMutex sync.Mutex 71 randomStream sha3.ShakeHash 72 } 73 74 // newPRNG generates a seed and creates a PRNG with that seed. 75 func newPRNG() (*prng, error) { 76 seed, err := NewPRNGSeed() 77 if err != nil { 78 return nil, err 79 } 80 return newPRNGWithSeed(seed) 81 } 82 83 // newPRNGWithSeed initializes a new PRNG using an existing seed. 84 func newPRNGWithSeed(seed *PRNGSeed) (*prng, error) { 85 shake := sha3.NewShake256() 86 _, err := shake.Write(seed[:]) 87 if err != nil { 88 return nil, err 89 } 90 p := &prng{ 91 randomStream: shake, 92 } 93 p.rand = rand.New(p) 94 return p, nil 95 } 96 97 // newPRNGWithSaltedSeed initializes a new PRNG using a seed derived from an 98 // existing seed and a salt with NewSaltedSeed. 99 func newPRNGWithSaltedSeed(seed *PRNGSeed, salt string) (*prng, error) { 100 saltedSeed, err := newSaltedPRNGSeed(seed, salt) 101 if err != nil { 102 return nil, err 103 } 104 return newPRNGWithSeed(saltedSeed) 105 } 106 107 // Read reads random bytes from the PRNG stream into b. Read conforms to 108 // io.Reader and always returns len(p), nil. 109 func (p *prng) Read(b []byte) (int, error) { 110 p.randomStreamMutex.Lock() 111 defer p.randomStreamMutex.Unlock() 112 113 // ShakeHash.Read never returns an error: 114 // https://godoc.org/golang.org/x/crypto/sha3#ShakeHash 115 _, _ = io.ReadFull(p.randomStream, b) 116 117 return len(b), nil 118 } 119 120 // Int63 is equivalent to math/read.Int63. 121 func (p *prng) Int63() int64 { 122 i := p.Uint64() 123 return int64(i & (1<<63 - 1)) 124 } 125 126 // Int63 is equivalent to math/read.Uint64. 127 func (p *prng) Uint64() uint64 { 128 var b [8]byte 129 p.Read(b[:]) 130 return binary.BigEndian.Uint64(b[:]) 131 } 132 133 // Seed must exist in order to use a PRNG as a math/rand.Source. This call is 134 // not supported and ignored. 135 func (p *prng) Seed(_ int64) { 136 } 137 138 // FlipWeightedCoin returns the result of a weighted 139 // random coin flip. If the weight is 0.5, the outcome 140 // is equally likely to be true or false. If the weight 141 // is 1.0, the outcome is always true, and if the 142 // weight is 0.0, the outcome is always false. 143 // 144 // Input weights > 1.0 are treated as 1.0. 145 func (p *prng) FlipWeightedCoin(weight float64) bool { 146 if weight > 1.0 { 147 weight = 1.0 148 } 149 f := float64(p.Int63()) / float64(math.MaxInt64) 150 return f > 1.0-weight 151 } 152 153 // Intn is equivalent to math/read.Intn, except it returns 0 if n <= 0 154 // instead of panicking. 155 func (p *prng) Intn(n int) int { 156 if n <= 0 { 157 return 0 158 } 159 return p.rand.Intn(n) 160 } 161 162 // Int63n is equivalent to math/read.Int63n, except it returns 0 if n <= 0 163 // instead of panicking. 164 func (p *prng) Int63n(n int64) int64 { 165 if n <= 0 { 166 return 0 167 } 168 return p.rand.Int63n(n) 169 } 170 171 // Intn is equivalent to math/read.Perm. 172 func (p *prng) Perm(n int) []int { 173 return p.rand.Perm(n) 174 } 175 176 // Range selects a random integer in [min, max]. 177 // If min < 0, min is set to 0. If max < min, min is returned. 178 func (p *prng) Range(min, max int) int { 179 if min < 0 { 180 min = 0 181 } 182 if max < min { 183 return min 184 } 185 n := p.Intn(max - min + 1) 186 n += min 187 return n 188 }