Skip to content

Commit 587c384

Browse files
FiloSottilegopherbot
authored andcommitted
math/rand/v2: add ChaCha8.Read
Fixes #67059 Closes #67452 Closes #67498 Change-Id: I84eba2ed787a17e9d6aaad2a8a78596e3944909a Reviewed-on: https://go-review.googlesource.com/c/go/+/587280 Reviewed-by: Roland Shoemaker <[email protected]> Auto-Submit: Filippo Valsorda <[email protected]> Reviewed-by: Carlos Amedee <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 6dd79d0 commit 587c384

File tree

5 files changed

+228
-2
lines changed

5 files changed

+228
-2
lines changed

api/next/67059.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pkg math/rand/v2, method (*ChaCha8) Read([]uint8) (int, error) #67059
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The new [ChaCha8.Read] method implements the [io.Reader] interface.

src/math/rand/rand.go

+1
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,7 @@ func Shuffle(n int, swap func(i, j int)) { globalRand().Shuffle(n, swap) }
474474
// Read, unlike the [Rand.Read] method, is safe for concurrent use.
475475
//
476476
// Deprecated: For almost all use cases, [crypto/rand.Read] is more appropriate.
477+
// If a deterministic source is required, use [math/rand/v2.ChaCha8.Read].
477478
func Read(p []byte) (n int, err error) { return globalRand().Read(p) }
478479

479480
// NormFloat64 returns a normally distributed float64 in the range

src/math/rand/v2/chacha8.go

+65-1
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,20 @@
44

55
package rand
66

7-
import "internal/chacha8rand"
7+
import (
8+
"errors"
9+
"internal/byteorder"
10+
"internal/chacha8rand"
11+
)
812

913
// A ChaCha8 is a ChaCha8-based cryptographically strong
1014
// random number generator.
1115
type ChaCha8 struct {
1216
state chacha8rand.State
17+
18+
// The last readLen bytes of readBuf are still to be consumed by Read.
19+
readBuf [8]byte
20+
readLen int // 0 <= readLen <= 8
1321
}
1422

1523
// NewChaCha8 returns a new ChaCha8 seeded with the given seed.
@@ -22,6 +30,8 @@ func NewChaCha8(seed [32]byte) *ChaCha8 {
2230
// Seed resets the ChaCha8 to behave the same way as NewChaCha8(seed).
2331
func (c *ChaCha8) Seed(seed [32]byte) {
2432
c.state.Init(seed)
33+
c.readLen = 0
34+
c.readBuf = [8]byte{}
2535
}
2636

2737
// Uint64 returns a uniformly distributed random uint64 value.
@@ -35,12 +45,66 @@ func (c *ChaCha8) Uint64() uint64 {
3545
}
3646
}
3747

48+
// Read reads exactly len(p) bytes into p.
49+
// It always returns len(p) and a nil error.
50+
//
51+
// If calls to Read and Uint64 are interleaved, the order in which bits are
52+
// returned by the two is undefined, and Read may return bits generated before
53+
// the last call to Uint64.
54+
func (c *ChaCha8) Read(p []byte) (n int, err error) {
55+
if c.readLen > 0 {
56+
n = copy(p, c.readBuf[len(c.readBuf)-c.readLen:])
57+
c.readLen -= n
58+
p = p[n:]
59+
}
60+
for len(p) >= 8 {
61+
byteorder.LePutUint64(p, c.Uint64())
62+
p = p[8:]
63+
n += 8
64+
}
65+
if len(p) > 0 {
66+
byteorder.LePutUint64(c.readBuf[:], c.Uint64())
67+
n += copy(p, c.readBuf[:])
68+
c.readLen = 8 - len(p)
69+
}
70+
return
71+
}
72+
3873
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
3974
func (c *ChaCha8) UnmarshalBinary(data []byte) error {
75+
data, ok := cutPrefix(data, []byte("readbuf:"))
76+
if ok {
77+
var buf []byte
78+
buf, data, ok = readUint8LengthPrefixed(data)
79+
if !ok {
80+
return errors.New("invalid ChaCha8 Read buffer encoding")
81+
}
82+
c.readLen = copy(c.readBuf[len(c.readBuf)-len(buf):], buf)
83+
}
4084
return chacha8rand.Unmarshal(&c.state, data)
4185
}
4286

87+
func cutPrefix(s, prefix []byte) (after []byte, found bool) {
88+
if len(s) < len(prefix) || string(s[:len(prefix)]) != string(prefix) {
89+
return s, false
90+
}
91+
return s[len(prefix):], true
92+
}
93+
94+
func readUint8LengthPrefixed(b []byte) (buf, rest []byte, ok bool) {
95+
if len(b) == 0 || len(b) < int(1+b[0]) {
96+
return nil, nil, false
97+
}
98+
return b[1 : 1+b[0]], b[1+b[0]:], true
99+
}
100+
43101
// MarshalBinary implements the encoding.BinaryMarshaler interface.
44102
func (c *ChaCha8) MarshalBinary() ([]byte, error) {
103+
if c.readLen > 0 {
104+
out := []byte("readbuf:")
105+
out = append(out, uint8(c.readLen))
106+
out = append(out, c.readBuf[len(c.readBuf)-c.readLen:]...)
107+
return append(out, chacha8rand.Marshal(&c.state)...), nil
108+
}
45109
return chacha8rand.Marshal(&c.state), nil
46110
}

src/math/rand/v2/chacha8_test.go

+160-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@
55
package rand_test
66

77
import (
8+
"bytes"
9+
"crypto/sha256"
10+
"encoding/hex"
11+
"io"
812
. "math/rand/v2"
913
"testing"
14+
"testing/iotest"
1015
)
1116

1217
func TestChaCha8(t *testing.T) {
@@ -25,6 +30,74 @@ func TestChaCha8(t *testing.T) {
2530
}
2631
}
2732

33+
func TestChaCha8Read(t *testing.T) {
34+
p := NewChaCha8(chacha8seed)
35+
h := sha256.New()
36+
37+
buf := make([]byte, chacha8outlen)
38+
if nn, err := p.Read(buf); err != nil {
39+
t.Fatal(err)
40+
} else if nn != len(buf) {
41+
t.Errorf("Read short: got %d, expected %d", nn, len(buf))
42+
}
43+
h.Write(buf)
44+
if got := h.Sum(nil); !bytes.Equal(got, chacha8hash) {
45+
t.Errorf("transcript incorrect: got %x, want %x", got, chacha8hash)
46+
}
47+
48+
p.Seed(chacha8seed)
49+
h.Reset()
50+
51+
buf = make([]byte, chacha8outlen)
52+
if _, err := io.ReadFull(iotest.OneByteReader(p), buf); err != nil {
53+
t.Errorf("one byte reads: %v", err)
54+
}
55+
h.Write(buf)
56+
if got := h.Sum(nil); !bytes.Equal(got, chacha8hash) {
57+
t.Errorf("transcript incorrect (one byte reads): got %x, want %x", got, chacha8hash)
58+
}
59+
60+
p.Seed(chacha8seed)
61+
h.Reset()
62+
63+
if n, err := p.Read(make([]byte, 0)); err != nil {
64+
t.Errorf("zero length read: %v", err)
65+
} else if n != 0 {
66+
t.Errorf("Read zero length: got %d, expected %d", n, 0)
67+
}
68+
69+
var n int
70+
for n < chacha8outlen {
71+
if IntN(2) == 0 {
72+
out, err := p.MarshalBinary()
73+
if err != nil {
74+
t.Fatal(err)
75+
}
76+
if IntN(2) == 0 {
77+
p = NewChaCha8([32]byte{})
78+
}
79+
if err := p.UnmarshalBinary(out); err != nil {
80+
t.Fatal(err)
81+
}
82+
}
83+
buf := make([]byte, IntN(100))
84+
if n+len(buf) > chacha8outlen {
85+
buf = buf[:chacha8outlen-n]
86+
}
87+
n += len(buf)
88+
t.Logf("reading %d bytes", len(buf))
89+
if nn, err := p.Read(buf); err != nil {
90+
t.Fatal(err)
91+
} else if nn != len(buf) {
92+
t.Errorf("Read short: got %d, expected %d", nn, len(buf))
93+
}
94+
h.Write(buf)
95+
}
96+
if got := h.Sum(nil); !bytes.Equal(got, chacha8hash) {
97+
t.Errorf("transcript incorrect: got %x, want %x", got, chacha8hash)
98+
}
99+
}
100+
28101
func TestChaCha8Marshal(t *testing.T) {
29102
p := NewChaCha8(chacha8seed)
30103
for i, x := range chacha8output {
@@ -33,7 +106,7 @@ func TestChaCha8Marshal(t *testing.T) {
33106
t.Fatalf("#%d: MarshalBinary: %v", i, err)
34107
}
35108
if string(enc) != chacha8marshal[i] {
36-
t.Fatalf("#%d: MarshalBinary=%q, want %q", i, enc, chacha8marshal[i])
109+
t.Errorf("#%d: MarshalBinary=%q, want %q", i, enc, chacha8marshal[i])
37110
}
38111
*p = ChaCha8{}
39112
if err := p.UnmarshalBinary(enc); err != nil {
@@ -45,6 +118,24 @@ func TestChaCha8Marshal(t *testing.T) {
45118
}
46119
}
47120

121+
func TestChaCha8MarshalRead(t *testing.T) {
122+
p := NewChaCha8(chacha8seed)
123+
for i := range 50 {
124+
enc, err := p.MarshalBinary()
125+
if err != nil {
126+
t.Fatalf("#%d: MarshalBinary: %v", i, err)
127+
}
128+
if string(enc) != chacha8marshalread[i] {
129+
t.Errorf("#%d: MarshalBinary=%q, want %q", i, enc, chacha8marshalread[i])
130+
}
131+
*p = ChaCha8{}
132+
if err := p.UnmarshalBinary(enc); err != nil {
133+
t.Fatalf("#%d: UnmarshalBinary: %v", i, err)
134+
}
135+
p.Read(make([]byte, 1))
136+
}
137+
}
138+
48139
func BenchmarkChaCha8(b *testing.B) {
49140
p := NewChaCha8([32]byte{1, 2, 3, 4, 5})
50141
var t uint64
@@ -54,11 +145,26 @@ func BenchmarkChaCha8(b *testing.B) {
54145
Sink = t
55146
}
56147

148+
func BenchmarkChaCha8Read(b *testing.B) {
149+
p := NewChaCha8([32]byte{1, 2, 3, 4, 5})
150+
buf := make([]byte, 32)
151+
b.SetBytes(32)
152+
var t uint8
153+
for n := b.N; n > 0; n-- {
154+
p.Read(buf)
155+
t += buf[0]
156+
}
157+
Sink = uint64(t)
158+
}
159+
57160
// Golden output test to make sure algorithm never changes,
58161
// so that its use in math/rand/v2 stays stable.
59162

60163
var chacha8seed = [32]byte([]byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ123456"))
61164

165+
var chacha8outlen = 2976
166+
var chacha8hash, _ = hex.DecodeString("bfec3d418b829afe5df2d8887d1508348409c293b73758d7efd841dd995fe021")
167+
62168
var chacha8output = []uint64{
63169
0xb773b6063d4616a5, 0x1160af22a66abc3c, 0x8c2599d9418d287c, 0x7ee07e037edc5cd6,
64170
0xcfaa9ee02d1c16ad, 0x0e090eef8febea79, 0x3c82d271128b5b3e, 0x9c5addc11252a34f,
@@ -529,3 +635,56 @@ var chacha8marshal = []string{
529635
"chacha8:\x00\x00\x00\x00\x00\x00\x00zK3\x9bB!,\x94\x9d\x975\xce'O_t\xee|\xb21\x87\xbb\xbb\xfd)\x8f\xe52\x01\vP\fk",
530636
"chacha8:\x00\x00\x00\x00\x00\x00\x00{K3\x9bB!,\x94\x9d\x975\xce'O_t\xee|\xb21\x87\xbb\xbb\xfd)\x8f\xe52\x01\vP\fk",
531637
}
638+
639+
var chacha8marshalread = []string{
640+
"chacha8:\x00\x00\x00\x00\x00\x00\x00\x00ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
641+
"readbuf:\a\x16F=\x06\xb6s\xb7chacha8:\x00\x00\x00\x00\x00\x00\x00\x01ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
642+
"readbuf:\x06F=\x06\xb6s\xb7chacha8:\x00\x00\x00\x00\x00\x00\x00\x01ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
643+
"readbuf:\x05=\x06\xb6s\xb7chacha8:\x00\x00\x00\x00\x00\x00\x00\x01ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
644+
"readbuf:\x04\x06\xb6s\xb7chacha8:\x00\x00\x00\x00\x00\x00\x00\x01ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
645+
"readbuf:\x03\xb6s\xb7chacha8:\x00\x00\x00\x00\x00\x00\x00\x01ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
646+
"readbuf:\x02s\xb7chacha8:\x00\x00\x00\x00\x00\x00\x00\x01ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
647+
"readbuf:\x01\xb7chacha8:\x00\x00\x00\x00\x00\x00\x00\x01ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
648+
"chacha8:\x00\x00\x00\x00\x00\x00\x00\x01ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
649+
"readbuf:\a\xbcj\xa6\"\xaf`\x11chacha8:\x00\x00\x00\x00\x00\x00\x00\x02ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
650+
"readbuf:\x06j\xa6\"\xaf`\x11chacha8:\x00\x00\x00\x00\x00\x00\x00\x02ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
651+
"readbuf:\x05\xa6\"\xaf`\x11chacha8:\x00\x00\x00\x00\x00\x00\x00\x02ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
652+
"readbuf:\x04\"\xaf`\x11chacha8:\x00\x00\x00\x00\x00\x00\x00\x02ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
653+
"readbuf:\x03\xaf`\x11chacha8:\x00\x00\x00\x00\x00\x00\x00\x02ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
654+
"readbuf:\x02`\x11chacha8:\x00\x00\x00\x00\x00\x00\x00\x02ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
655+
"readbuf:\x01\x11chacha8:\x00\x00\x00\x00\x00\x00\x00\x02ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
656+
"chacha8:\x00\x00\x00\x00\x00\x00\x00\x02ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
657+
"readbuf:\a(\x8dAٙ%\x8cchacha8:\x00\x00\x00\x00\x00\x00\x00\x03ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
658+
"readbuf:\x06\x8dAٙ%\x8cchacha8:\x00\x00\x00\x00\x00\x00\x00\x03ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
659+
"readbuf:\x05Aٙ%\x8cchacha8:\x00\x00\x00\x00\x00\x00\x00\x03ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
660+
"readbuf:\x04ٙ%\x8cchacha8:\x00\x00\x00\x00\x00\x00\x00\x03ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
661+
"readbuf:\x03\x99%\x8cchacha8:\x00\x00\x00\x00\x00\x00\x00\x03ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
662+
"readbuf:\x02%\x8cchacha8:\x00\x00\x00\x00\x00\x00\x00\x03ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
663+
"readbuf:\x01\x8cchacha8:\x00\x00\x00\x00\x00\x00\x00\x03ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
664+
"chacha8:\x00\x00\x00\x00\x00\x00\x00\x03ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
665+
"readbuf:\a\\\xdc~\x03~\xe0~chacha8:\x00\x00\x00\x00\x00\x00\x00\x04ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
666+
"readbuf:\x06\xdc~\x03~\xe0~chacha8:\x00\x00\x00\x00\x00\x00\x00\x04ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
667+
"readbuf:\x05~\x03~\xe0~chacha8:\x00\x00\x00\x00\x00\x00\x00\x04ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
668+
"readbuf:\x04\x03~\xe0~chacha8:\x00\x00\x00\x00\x00\x00\x00\x04ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
669+
"readbuf:\x03~\xe0~chacha8:\x00\x00\x00\x00\x00\x00\x00\x04ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
670+
"readbuf:\x02\xe0~chacha8:\x00\x00\x00\x00\x00\x00\x00\x04ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
671+
"readbuf:\x01~chacha8:\x00\x00\x00\x00\x00\x00\x00\x04ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
672+
"chacha8:\x00\x00\x00\x00\x00\x00\x00\x04ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
673+
"readbuf:\a\x16\x1c-\xe0\x9e\xaa\xcfchacha8:\x00\x00\x00\x00\x00\x00\x00\x05ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
674+
"readbuf:\x06\x1c-\xe0\x9e\xaa\xcfchacha8:\x00\x00\x00\x00\x00\x00\x00\x05ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
675+
"readbuf:\x05-\xe0\x9e\xaa\xcfchacha8:\x00\x00\x00\x00\x00\x00\x00\x05ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
676+
"readbuf:\x04\xe0\x9e\xaa\xcfchacha8:\x00\x00\x00\x00\x00\x00\x00\x05ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
677+
"readbuf:\x03\x9e\xaa\xcfchacha8:\x00\x00\x00\x00\x00\x00\x00\x05ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
678+
"readbuf:\x02\xaa\xcfchacha8:\x00\x00\x00\x00\x00\x00\x00\x05ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
679+
"readbuf:\x01\xcfchacha8:\x00\x00\x00\x00\x00\x00\x00\x05ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
680+
"chacha8:\x00\x00\x00\x00\x00\x00\x00\x05ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
681+
"readbuf:\a\xea\xeb\x8f\xef\x0e\t\x0echacha8:\x00\x00\x00\x00\x00\x00\x00\x06ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
682+
"readbuf:\x06\xeb\x8f\xef\x0e\t\x0echacha8:\x00\x00\x00\x00\x00\x00\x00\x06ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
683+
"readbuf:\x05\x8f\xef\x0e\t\x0echacha8:\x00\x00\x00\x00\x00\x00\x00\x06ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
684+
"readbuf:\x04\xef\x0e\t\x0echacha8:\x00\x00\x00\x00\x00\x00\x00\x06ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
685+
"readbuf:\x03\x0e\t\x0echacha8:\x00\x00\x00\x00\x00\x00\x00\x06ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
686+
"readbuf:\x02\t\x0echacha8:\x00\x00\x00\x00\x00\x00\x00\x06ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
687+
"readbuf:\x01\x0echacha8:\x00\x00\x00\x00\x00\x00\x00\x06ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
688+
"chacha8:\x00\x00\x00\x00\x00\x00\x00\x06ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
689+
"readbuf:\a[\x8b\x12q҂<chacha8:\x00\x00\x00\x00\x00\x00\x00\aABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
690+
}

0 commit comments

Comments
 (0)