chacha8.mx raw
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 rand
6
7 import (
8 "errors"
9 "internal/byteorder"
10 "internal/chacha8rand"
11 )
12
13 // A ChaCha8 is a ChaCha8-based cryptographically strong
14 // random number generator.
15 type ChaCha8 struct {
16 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
21 }
22
23 // NewChaCha8 returns a new ChaCha8 seeded with the given seed.
24 func NewChaCha8(seed [32]byte) *ChaCha8 {
25 c := &ChaCha8{}
26 c.state.Init(seed)
27 return c
28 }
29
30 // Seed resets the ChaCha8 to behave the same way as NewChaCha8(seed).
31 func (c *ChaCha8) Seed(seed [32]byte) {
32 c.state.Init(seed)
33 c.readLen = 0
34 c.readBuf = [8]byte{}
35 }
36
37 // Uint64 returns a uniformly distributed random uint64 value.
38 func (c *ChaCha8) Uint64() uint64 {
39 for {
40 x, ok := c.state.Next()
41 if ok {
42 return x
43 }
44 c.state.Refill()
45 }
46 }
47
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
73 // UnmarshalBinary implements the [encoding.BinaryUnmarshaler] interface.
74 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 }
84 return chacha8rand.Unmarshal(&c.state, data)
85 }
86
87 func cutPrefix(s, prefix []byte) (after []byte, found bool) {
88 if len(s) < len(prefix) || s[:len(prefix)] != 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
101 // AppendBinary implements the [encoding.BinaryAppender] interface.
102 func (c *ChaCha8) AppendBinary(b []byte) ([]byte, error) {
103 if c.readLen > 0 {
104 b = append(b, "readbuf:"...)
105 b = append(b, uint8(c.readLen))
106 b = append(b, c.readBuf[len(c.readBuf)-c.readLen:]...)
107 }
108 return append(b, chacha8rand.Marshal(&c.state)...), nil
109 }
110
111 // MarshalBinary implements the [encoding.BinaryMarshaler] interface.
112 func (c *ChaCha8) MarshalBinary() ([]byte, error) {
113 // the maximum length of (chacha8rand.Marshal + c.readBuf + "readbuf:") is 64
114 return c.AppendBinary([]byte{:0:64})
115 }
116