chacha20.go raw

   1  // Package chacha20 implements the ChaCha20 stream cipher as specified in
   2  // RFC 8439.
   3  //
   4  // The block counter starts at zero, as required by NIP-44. The package is
   5  // pure Go and uses no architecture-specific code, so it builds with TinyGo.
   6  package chacha20
   7  
   8  import (
   9  	"encoding/binary"
  10  	"math/bits"
  11  )
  12  
  13  const (
  14  	// KeySize is the length of a ChaCha20 key in bytes.
  15  	KeySize = 32
  16  	// NonceSize is the length of an RFC 8439 nonce in bytes.
  17  	NonceSize = 12
  18  	// BlockSize is the number of keystream bytes produced per block.
  19  	BlockSize = 64
  20  )
  21  
  22  // sigma holds the ChaCha20 constant "expand 32-byte k", one word per
  23  // little-endian byte quadruple.
  24  var sigma = [4]uint32{0x61707865, 0x3320646e, 0x79622d32, 0x6b206574}
  25  
  26  // quarter performs one ChaCha20 quarter round on four state words.
  27  func quarter(a, b, c, d uint32) (uint32, uint32, uint32, uint32) {
  28  	a += b
  29  	d ^= a
  30  	d = bits.RotateLeft32(d, 16)
  31  	c += d
  32  	b ^= c
  33  	b = bits.RotateLeft32(b, 12)
  34  	a += b
  35  	d ^= a
  36  	d = bits.RotateLeft32(d, 8)
  37  	c += d
  38  	b ^= c
  39  	b = bits.RotateLeft32(b, 7)
  40  	return a, b, c, d
  41  }
  42  
  43  // initState builds the initial ChaCha20 state for the given key, counter and
  44  // nonce.
  45  func initState(key *[KeySize]byte, counter uint32, nonce *[NonceSize]byte) [16]uint32 {
  46  	var s [16]uint32
  47  	s[0], s[1], s[2], s[3] = sigma[0], sigma[1], sigma[2], sigma[3]
  48  	for i := 0; i < 8; i++ {
  49  		s[4+i] = binary.LittleEndian.Uint32(key[4*i : 4*i+4])
  50  	}
  51  	s[12] = counter
  52  	for i := 0; i < 3; i++ {
  53  		s[13+i] = binary.LittleEndian.Uint32(nonce[4*i : 4*i+4])
  54  	}
  55  	return s
  56  }
  57  
  58  // Block returns the 64-byte ChaCha20 keystream block for the given key,
  59  // block counter and nonce.
  60  func Block(key *[KeySize]byte, counter uint32, nonce *[NonceSize]byte) [BlockSize]byte {
  61  	state := initState(key, counter, nonce)
  62  	x := state
  63  
  64  	// Ten double rounds: column rounds followed by diagonal rounds.
  65  	for i := 0; i < 10; i++ {
  66  		x[0], x[4], x[8], x[12] = quarter(x[0], x[4], x[8], x[12])
  67  		x[1], x[5], x[9], x[13] = quarter(x[1], x[5], x[9], x[13])
  68  		x[2], x[6], x[10], x[14] = quarter(x[2], x[6], x[10], x[14])
  69  		x[3], x[7], x[11], x[15] = quarter(x[3], x[7], x[11], x[15])
  70  		x[0], x[5], x[10], x[15] = quarter(x[0], x[5], x[10], x[15])
  71  		x[1], x[6], x[11], x[12] = quarter(x[1], x[6], x[11], x[12])
  72  		x[2], x[7], x[8], x[13] = quarter(x[2], x[7], x[8], x[13])
  73  		x[3], x[4], x[9], x[14] = quarter(x[3], x[4], x[9], x[14])
  74  	}
  75  
  76  	var out [BlockSize]byte
  77  	for i := 0; i < 16; i++ {
  78  		binary.LittleEndian.PutUint32(out[4*i:4*i+4], x[i]+state[i])
  79  	}
  80  	return out
  81  }
  82  
  83  // XORKeyStream XORs each byte of src with the ChaCha20 keystream derived from
  84  // key and nonce, writing the result to dst. The block counter starts at zero
  85  // and is incremented for every 64-byte block.
  86  //
  87  // dst must be at least as long as src. dst and src may be the same slice, or
  88  // may be non-overlapping; they must not partially overlap.
  89  func XORKeyStream(key *[KeySize]byte, nonce *[NonceSize]byte, dst, src []byte) {
  90  	if len(dst) < len(src) {
  91  		panic("chacha20: output smaller than input")
  92  	}
  93  	var counter uint32
  94  	for len(src) > 0 {
  95  		ks := Block(key, counter, nonce)
  96  		counter++
  97  		n := len(src)
  98  		if n > BlockSize {
  99  			n = BlockSize
 100  		}
 101  		for i := 0; i < n; i++ {
 102  			dst[i] = src[i] ^ ks[i]
 103  		}
 104  		dst = dst[n:]
 105  		src = src[n:]
 106  	}
 107  }
 108