1 // Copyright 2023 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4 5 // Package chacha8rand implements a pseudorandom generator
6 // based on ChaCha8. It is used by both runtime and math/rand/v2
7 // and must have minimal dependencies.
8 package chacha8rand
9 10 import (
11 "internal/byteorder"
12 "internal/cpu"
13 "unsafe"
14 )
15 16 // Offsets into internal/cpu records for use in assembly.
17 const (
18 offsetLOONG64HasLSX = unsafe.Offsetof(cpu.Loong64.HasLSX)
19 )
20 21 const (
22 ctrInc = 4 // increment counter by 4 between block calls
23 ctrMax = 16 // reseed when counter reaches 16
24 chunk = 32 // each chunk produced by block is 32 uint64s
25 reseed = 4 // reseed with 4 words
26 )
27 28 // block is the chacha8rand block function.
29 func block(seed *[4]uint64, blocks *[32]uint64, counter uint32)
30 31 // A State holds the state for a single random generator.
32 // It must be used from one goroutine at a time.
33 // If used by multiple goroutines at a time, the goroutines
34 // may see the same random values, but the code will not
35 // crash or cause out-of-bounds memory accesses.
36 type State struct {
37 buf [32]uint64
38 seed [4]uint64
39 i uint32
40 n uint32
41 c uint32
42 }
43 44 // Next returns the next random value, along with a boolean
45 // indicating whether one was available.
46 // If one is not available, the caller should call Refill
47 // and then repeat the call to Next.
48 //
49 // Next is //go:nosplit to allow its use in the runtime
50 // with per-m data without holding the per-m lock.
51 //
52 //go:nosplit
53 func (s *State) Next() (uint64, bool) {
54 i := s.i
55 if i >= s.n {
56 return 0, false
57 }
58 s.i = i + 1
59 return s.buf[i&31], true // i&31 eliminates bounds check
60 }
61 62 // Init seeds the State with the given seed value.
63 func (s *State) Init(seed [32]byte) {
64 s.Init64([4]uint64{
65 byteorder.LEUint64(seed[0*8:]),
66 byteorder.LEUint64(seed[1*8:]),
67 byteorder.LEUint64(seed[2*8:]),
68 byteorder.LEUint64(seed[3*8:]),
69 })
70 }
71 72 // Init64 seeds the state with the given seed value.
73 func (s *State) Init64(seed [4]uint64) {
74 s.seed = seed
75 block(&s.seed, &s.buf, 0)
76 s.c = 0
77 s.i = 0
78 s.n = chunk
79 }
80 81 // Refill refills the state with more random values.
82 // After a call to Refill, an immediate call to Next will succeed
83 // (unless multiple goroutines are incorrectly sharing a state).
84 func (s *State) Refill() {
85 s.c += ctrInc
86 if s.c == ctrMax {
87 // Reseed with generated uint64s for forward secrecy.
88 // Normally this is done immediately after computing a block,
89 // but we do it immediately before computing the next block,
90 // to allow a much smaller serialized state (just the seed plus offset).
91 // This gives a delayed benefit for the forward secrecy
92 // (you can reconstruct the recent past given a memory dump),
93 // which we deem acceptable in exchange for the reduced size.
94 s.seed[0] = s.buf[len(s.buf)-reseed+0]
95 s.seed[1] = s.buf[len(s.buf)-reseed+1]
96 s.seed[2] = s.buf[len(s.buf)-reseed+2]
97 s.seed[3] = s.buf[len(s.buf)-reseed+3]
98 s.c = 0
99 }
100 block(&s.seed, &s.buf, s.c)
101 s.i = 0
102 s.n = uint32(len(s.buf))
103 if s.c == ctrMax-ctrInc {
104 s.n = uint32(len(s.buf)) - reseed
105 }
106 }
107 108 // Reseed reseeds the state with new random values.
109 // After a call to Reseed, any previously returned random values
110 // have been erased from the memory of the state and cannot be
111 // recovered.
112 func (s *State) Reseed() {
113 var seed [4]uint64
114 for i := range seed {
115 for {
116 x, ok := s.Next()
117 if ok {
118 seed[i] = x
119 break
120 }
121 s.Refill()
122 }
123 }
124 s.Init64(seed)
125 }
126 127 // Marshal marshals the state into a byte slice.
128 // Marshal and Unmarshal are functions, not methods,
129 // so that they will not be linked into the runtime
130 // when it uses the State struct, since the runtime
131 // does not need these.
132 func Marshal(s *State) []byte {
133 data := []byte{:6*8}
134 copy(data, "chacha8:")
135 used := (s.c/ctrInc)*chunk + s.i
136 byteorder.BEPutUint64(data[1*8:], uint64(used))
137 for i, seed := range s.seed {
138 byteorder.LEPutUint64(data[(2+i)*8:], seed)
139 }
140 return data
141 }
142 143 type errUnmarshalChaCha8 struct{}
144 145 func (*errUnmarshalChaCha8) Error() string {
146 return "invalid ChaCha8 encoding"
147 }
148 149 // Unmarshal unmarshals the state from a byte slice.
150 func Unmarshal(s *State, data []byte) error {
151 if len(data) != 6*8 || []byte(data[:8]) != "chacha8:" {
152 return &errUnmarshalChaCha8{}
153 }
154 used := byteorder.BEUint64(data[1*8:])
155 if used > (ctrMax/ctrInc)*chunk-reseed {
156 return &errUnmarshalChaCha8{}
157 }
158 for i := range s.seed {
159 s.seed[i] = byteorder.LEUint64(data[(2+i)*8:])
160 }
161 s.c = ctrInc * (uint32(used) / chunk)
162 block(&s.seed, &s.buf, s.c)
163 s.i = uint32(used) % chunk
164 s.n = chunk
165 if s.c == ctrMax-ctrInc {
166 s.n = chunk - reseed
167 }
168 return nil
169 }
170