diff --git a/api/next/67059.txt b/api/next/67059.txt new file mode 100644 index 00000000000000..c128585d14fc68 --- /dev/null +++ b/api/next/67059.txt @@ -0,0 +1 @@ +pkg math/rand/v2, method (*ChaCha8) Read([]uint8) (int, error) #67059 diff --git a/doc/next/6-stdlib/99-minor/math/rand/v2/67059.md b/doc/next/6-stdlib/99-minor/math/rand/v2/67059.md new file mode 100644 index 00000000000000..917959b390afcd --- /dev/null +++ b/doc/next/6-stdlib/99-minor/math/rand/v2/67059.md @@ -0,0 +1 @@ +[ChaCha8] source implements the [io.Reader] interface. diff --git a/src/internal/chacha8rand/chacha8.go b/src/internal/chacha8rand/chacha8.go index 8f1b4e5315c99e..569bc601f41e4c 100644 --- a/src/internal/chacha8rand/chacha8.go +++ b/src/internal/chacha8rand/chacha8.go @@ -7,7 +7,11 @@ // and must have minimal dependencies. package chacha8rand -import "internal/byteorder" +import ( + "internal/byteorder" + "internal/goarch" + "unsafe" +) const ( ctrInc = 4 // increment counter by 4 between block calls @@ -158,3 +162,40 @@ func Unmarshal(s *State, data []byte) error { } return nil } + +func (s *State) FillRand(b []byte) { + if s.i == s.n { + s.Refill() + } + + for { + curRand := s.buf[s.i:s.n] + + // Make sure that on little and big endian systems the bytes are filled in the same order. + if goarch.BigEndian { + for _, v := range curRand { + var tmp [8]byte + byteorder.LePutUint64(tmp[:], v) + n := copy(b, tmp[:]) + s.i++ + b = b[n:] + + if len(b) == 0 { + return + } + } + s.Refill() + continue + } + + curRandBytes := unsafe.Slice((*byte)(unsafe.Pointer(&curRand[0])), len(curRand)*8) + n := copy(b, curRandBytes) + s.i += (uint32(n) + 7) / 8 + b = b[n:] + + if len(b) == 0 { + return + } + s.Refill() + } +} diff --git a/src/internal/chacha8rand/rand_test.go b/src/internal/chacha8rand/rand_test.go index 2975013bfa9cde..0e89a02c7bc037 100644 --- a/src/internal/chacha8rand/rand_test.go +++ b/src/internal/chacha8rand/rand_test.go @@ -8,6 +8,7 @@ import ( "bytes" "encoding/binary" "fmt" + "internal/byteorder" . "internal/chacha8rand" "slices" "testing" @@ -30,6 +31,38 @@ func TestOutput(t *testing.T) { } } +func TestFillRand(t *testing.T) { + expect := make([]byte, 0, len(output)*8) + for _, v := range output { + expect = byteorder.LeAppendUint64(expect, v) + } + + cases := []struct { + expect [][]byte + }{ + {[][]byte{expect}}, + {[][]byte{expect[:1], expect[8:9]}}, + {[][]byte{expect[:4], expect[8:12]}}, + {[][]byte{expect[:4], expect[8:12]}}, + {[][]byte{expect[:8], expect[8:16]}}, + {[][]byte{expect[:128], expect[128:256]}}, + {[][]byte{expect[:128], expect[128:]}}, + {[][]byte{expect[:100], expect[104:]}}, + } + + var s State + for _, tt := range cases { + s.Init(seed) + for _, expect := range tt.expect { + got := make([]byte, len(expect)) + s.FillRand(got) + if !bytes.Equal(expect, got) { + t.Errorf("got = %#x; want = %#x", got, expect) + } + } + } +} + func TestMarshal(t *testing.T) { var s State s.Init(seed) diff --git a/src/math/rand/v2/chacha8.go b/src/math/rand/v2/chacha8.go index 6b9aa7278250f8..94e15954cf59f6 100644 --- a/src/math/rand/v2/chacha8.go +++ b/src/math/rand/v2/chacha8.go @@ -44,3 +44,10 @@ func (c *ChaCha8) UnmarshalBinary(data []byte) error { func (c *ChaCha8) MarshalBinary() ([]byte, error) { return chacha8rand.Marshal(&c.state), nil } + +// Read generates len(p) random bytes and writes them into p. +// It never returns an error. +func (c *ChaCha8) Read(p []byte) (n int, err error) { + c.state.FillRand(p) + return len(p), nil +} diff --git a/src/math/rand/v2/chacha8_test.go b/src/math/rand/v2/chacha8_test.go index 2c55b479b25fe7..ba57f2d80ad947 100644 --- a/src/math/rand/v2/chacha8_test.go +++ b/src/math/rand/v2/chacha8_test.go @@ -6,6 +6,7 @@ package rand_test import ( . "math/rand/v2" + "strconv" "testing" ) @@ -529,3 +530,30 @@ var chacha8marshal = []string{ "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", "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", } + +func TestChaCha8Read(t *testing.T) { + c := NewChaCha8(chacha8seed) + n, err := c.Read(make([]byte, 128)) + if n != 128 || err != nil { + t.Errorf("(*ChaCha8).Read(buf) = (%v, %v); want = (128, nil)", n, err) + } + + n, err = c.Read(make([]byte, 0)) + if n != 0 || err != nil { + t.Errorf("(*ChaCha8).Read(buf) = (%v, %v); want = (0, nil)", n, err) + } +} + +func BenchmarkRead(b *testing.B) { + for _, v := range []int{1, 2, 3, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 1024 * 2, 1024 * 16} { + b.Run(strconv.FormatInt(int64(v), 10), func(b *testing.B) { + r := NewChaCha8(chacha8seed) + buf := make([]byte, v) + b.SetBytes(int64(v)) + b.ResetTimer() + for n := b.N; n > 0; n-- { + r.Read(buf) + } + }) + } +}