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