Skip to content

math/rand/v2: implement io.Reader for ChaCha8 #67452

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/next/67059.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pkg math/rand/v2, method (*ChaCha8) Read(p []byte) (n int, err error) #67059
36 changes: 36 additions & 0 deletions src/internal/chacha8rand/chacha8.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ type State struct {
i uint32
n uint32
c uint32

// readVal contains remainder of 63-bit integer used for bytes
// generation during most recent Read call.
// It is saved so next Read call can start where the previous
// one finished.
readVal uint64
// readPos indicates the number of low-order bytes of readVal
// that are still valid.
readPos int8
}

// Next returns the next random value, along with a boolean
Expand Down Expand Up @@ -66,6 +75,8 @@ func (s *State) Init64(seed [4]uint64) {
block(&s.seed, &s.buf, 0)
s.c = 0
s.i = 0
s.readPos = 0
s.readVal = 0
s.n = chunk
}

Expand Down Expand Up @@ -158,3 +169,28 @@ func Unmarshal(s *State, data []byte) error {
}
return nil
}

// Read reads random bytes from the state into p.
func Read(s *State, p []byte) (n int) {
pos := s.readPos
val := s.readVal
var ok bool
for n = 0; n < len(p); n++ {
if pos == 0 {
for {
val, ok = s.Next()
if ok {
break
}
s.Refill()
}
pos = 7
}
p[n] = byte(val)
val >>= 8
pos--
}
s.readPos = pos
s.readVal = val
return
}
5 changes: 5 additions & 0 deletions src/math/rand/v2/chacha8.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,8 @@ 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.
func (c *ChaCha8) Read(p []byte) (n int, err error) {
return chacha8rand.Read(&c.state, p), nil
}
82 changes: 82 additions & 0 deletions src/math/rand/v2/chacha8_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
package rand_test

import (
"bytes"
"io"
. "math/rand/v2"
"testing"
"testing/iotest"
)

func TestChaCha8(t *testing.T) {
Expand Down Expand Up @@ -529,3 +532,82 @@ 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 TestReadEmpty(t *testing.T) {
r := NewChaCha8(chacha8seed)
buf := make([]byte, 0)
n, err := r.Read(buf)
if err != nil {
t.Errorf("Read err into empty buffer; %v", err)
}
if n != 0 {
t.Errorf("Read into empty buffer returned unexpected n of %d", n)
}
}

func TestReadSeedReset(t *testing.T) {
r := NewChaCha8(chacha8seed)
b1 := make([]byte, 128)
_, err := r.Read(b1)
if err != nil {
t.Errorf("read: %v", err)
}

r.Seed(chacha8seed)
b2 := make([]byte, 128)
_, err = r.Read(b2)
if err != nil {
t.Errorf("read: %v", err)
}
if !bytes.Equal(b1, b2) {
t.Errorf("mismatch after re-seed:\n%x\n%x", b1, b2)
}
}

func TestReadByOneByte(t *testing.T) {
r := NewChaCha8(chacha8seed)
b1 := make([]byte, 100)
_, err := io.ReadFull(iotest.OneByteReader(r), b1)
if err != nil {
t.Errorf("read by one byte: %v", err)
}
r = NewChaCha8(chacha8seed)
b2 := make([]byte, 100)
_, err = r.Read(b2)
if err != nil {
t.Errorf("read: %v", err)
}
if !bytes.Equal(b1, b2) {
t.Errorf("read by one byte vs single read:\n%x\n%x", b1, b2)
}
}

func BenchmarkRead3(b *testing.B) {
r := NewChaCha8(chacha8seed)
buf := make([]byte, 3)
b.ResetTimer()
b.SetBytes(3)
for n := b.N; n > 0; n-- {
r.Read(buf)
}
}

func BenchmarkRead64(b *testing.B) {
r := NewChaCha8(chacha8seed)
buf := make([]byte, 64)
b.ResetTimer()
b.SetBytes(64)
for n := b.N; n > 0; n-- {
r.Read(buf)
}
}

func BenchmarkRead1000(b *testing.B) {
r := NewChaCha8(chacha8seed)
buf := make([]byte, 1000)
b.ResetTimer()
b.SetBytes(1000)
for n := b.N; n > 0; n-- {
r.Read(buf)
}
}