From 5a4b225e8c7e25334ae7d7bd03d96992adba953e Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Sat, 18 May 2024 09:52:57 +0200 Subject: [PATCH 1/6] math/rand/v2: add Read method to ChaCha8 source Change-Id: I626b4cdcee75d56ef3d49c2abac765cde0b93999 --- src/internal/chacha8rand/chacha8.go | 43 ++++++++++++++++++++++++++- src/internal/chacha8rand/rand_test.go | 18 +++++++++++ src/math/rand/v2/chacha8.go | 6 ++++ src/math/rand/v2/chacha8_test.go | 15 ++++++++++ 4 files changed, 81 insertions(+), 1 deletion(-) diff --git a/src/internal/chacha8rand/chacha8.go b/src/internal/chacha8rand/chacha8.go index 8f1b4e5315c99e..2fec803f6128fa 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 { + break + } + } + 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 { + break + } + s.Refill() + } +} diff --git a/src/internal/chacha8rand/rand_test.go b/src/internal/chacha8rand/rand_test.go index 2975013bfa9cde..ac8cd70d8686f2 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,23 @@ func TestOutput(t *testing.T) { } } +func TestOutputBytes(t *testing.T) { + var s State + s.Init(seed) + + expect := make([]byte, 0, len(output)*8) + for _, v := range output { + expect = byteorder.LeAppendUint64(expect, v) + } + + 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..044a38e1c10994 100644 --- a/src/math/rand/v2/chacha8.go +++ b/src/math/rand/v2/chacha8.go @@ -44,3 +44,9 @@ 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) { + 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..a6b3602a3d1d0d 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,17 @@ 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 BenchmarkRead(b *testing.B) { + for _, v := range []int{1, 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) + } + }) + } +} From 9cea1916895e7f52535c8f4ad555ea0a95d1cdfe Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Sat, 18 May 2024 16:26:07 +0200 Subject: [PATCH 2/6] add api and relnotes Change-Id: I017c46eb13e5838c74b4faa51c143f7d43f12584 --- api/next/67059.txt | 1 + doc/next/6-stdlib/99-minor/math/rand/v2/67059.md | 1 + 2 files changed, 2 insertions(+) create mode 100644 api/next/67059.txt create mode 100644 doc/next/6-stdlib/99-minor/math/rand/v2/67059.md diff --git a/api/next/67059.txt b/api/next/67059.txt new file mode 100644 index 00000000000000..e0edb69c73e58c --- /dev/null +++ b/api/next/67059.txt @@ -0,0 +1 @@ +pkg math/rand/v2, method (*ChaCha8) Read(p []byte) (n int, err 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. From 18c2a41a3961ca48d7563b4de541a1d18d3ae466 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Sat, 18 May 2024 17:04:38 +0200 Subject: [PATCH 3/6] update tests Change-Id: I2d7f4a902877c209b6aa793f108856b750f25d8b --- src/internal/chacha8rand/rand_test.go | 31 ++++++++++++++++++++------- src/math/rand/v2/chacha8_test.go | 15 ++++++++++++- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/internal/chacha8rand/rand_test.go b/src/internal/chacha8rand/rand_test.go index ac8cd70d8686f2..0e89a02c7bc037 100644 --- a/src/internal/chacha8rand/rand_test.go +++ b/src/internal/chacha8rand/rand_test.go @@ -31,20 +31,35 @@ func TestOutput(t *testing.T) { } } -func TestOutputBytes(t *testing.T) { - var s State - s.Init(seed) - +func TestFillRand(t *testing.T) { expect := make([]byte, 0, len(output)*8) for _, v := range output { expect = byteorder.LeAppendUint64(expect, v) } - got := make([]byte, len(expect)) - s.FillRand(got) + 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:]}}, + } - if !bytes.Equal(expect, got) { - t.Errorf("got = %#x; want = %#x", got, expect) + 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) + } + } } } diff --git a/src/math/rand/v2/chacha8_test.go b/src/math/rand/v2/chacha8_test.go index a6b3602a3d1d0d..ba57f2d80ad947 100644 --- a/src/math/rand/v2/chacha8_test.go +++ b/src/math/rand/v2/chacha8_test.go @@ -531,8 +531,21 @@ var chacha8marshal = []string{ "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, 3, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 1024 * 2, 1024 * 16} { + 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) From 05e33869cf594e063bf949ae1b1fa32634cd51c7 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Sat, 18 May 2024 17:24:17 +0200 Subject: [PATCH 4/6] fix api file Change-Id: I2d4f667000c78998ed783da0786c93049b7dcc2f --- api/next/67059.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/next/67059.txt b/api/next/67059.txt index e0edb69c73e58c..c128585d14fc68 100644 --- a/api/next/67059.txt +++ b/api/next/67059.txt @@ -1 +1 @@ -pkg math/rand/v2, method (*ChaCha8) Read(p []byte) (n int, err error) #67059 +pkg math/rand/v2, method (*ChaCha8) Read([]uint8) (int, error) #67059 From 9cd019b64ac3acf7b5b0dcd4d0622d1981bdd0b8 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Sat, 18 May 2024 17:27:21 +0200 Subject: [PATCH 5/6] update docs Change-Id: Iaedf3c4dc5220679ed40cfcf3ce9d30461e828e0 --- src/math/rand/v2/chacha8.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/math/rand/v2/chacha8.go b/src/math/rand/v2/chacha8.go index 044a38e1c10994..94e15954cf59f6 100644 --- a/src/math/rand/v2/chacha8.go +++ b/src/math/rand/v2/chacha8.go @@ -46,6 +46,7 @@ func (c *ChaCha8) MarshalBinary() ([]byte, error) { } // 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 From e3db1b8158dd5edb7139109066d920662b72c208 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Sun, 19 May 2024 11:48:38 +0200 Subject: [PATCH 6/6] fix endless loop on BigEndian systems Change-Id: I330a6cfb8d90c00ad86f457ea6b54c6197dffd15 --- src/internal/chacha8rand/chacha8.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/internal/chacha8rand/chacha8.go b/src/internal/chacha8rand/chacha8.go index 2fec803f6128fa..569bc601f41e4c 100644 --- a/src/internal/chacha8rand/chacha8.go +++ b/src/internal/chacha8rand/chacha8.go @@ -181,7 +181,7 @@ func (s *State) FillRand(b []byte) { b = b[n:] if len(b) == 0 { - break + return } } s.Refill() @@ -194,7 +194,7 @@ func (s *State) FillRand(b []byte) { b = b[n:] if len(b) == 0 { - break + return } s.Refill() }