github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/gnovm/stdlibs/crypto/chacha20/chacha/chacha.gno (about)

     1  // Copyright (c) 2016 Andreas Auernhammer. All rights reserved.
     2  // Use of this source code is governed by a license that can be
     3  // found in the LICENSE file.
     4  
     5  // Package chacha implements some low-level functions of the
     6  // ChaCha cipher family.
     7  package chacha // import "github.com/aead/chacha20/chacha"
     8  
     9  import (
    10  	"encoding/binary"
    11  	"errors"
    12  	"math"
    13  )
    14  
    15  const (
    16  	// NonceSize is the size of the ChaCha20 nonce in bytes.
    17  	NonceSize = 8
    18  
    19  	// INonceSize is the size of the IETF-ChaCha20 nonce in bytes.
    20  	INonceSize = 12
    21  
    22  	// XNonceSize is the size of the XChaCha20 nonce in bytes.
    23  	XNonceSize = 24
    24  
    25  	// KeySize is the size of the key in bytes.
    26  	KeySize = 32
    27  )
    28  
    29  var (
    30  	useSSE2  bool
    31  	useSSSE3 bool
    32  	useAVX   bool
    33  	useAVX2  bool
    34  )
    35  
    36  var (
    37  	errKeySize      = errors.New("chacha20/chacha: bad key length")
    38  	errInvalidNonce = errors.New("chacha20/chacha: bad nonce length")
    39  )
    40  
    41  func setup(state *[64]byte, nonce, key []byte) (err error) {
    42  	if len(key) != KeySize {
    43  		err = errKeySize
    44  		return
    45  	}
    46  	var Nonce [16]byte
    47  	switch len(nonce) {
    48  	case NonceSize:
    49  		copy(Nonce[8:], nonce)
    50  		initialize(state, key, &Nonce)
    51  	case INonceSize:
    52  		copy(Nonce[4:], nonce)
    53  		initialize(state, key, &Nonce)
    54  	case XNonceSize:
    55  		var tmpKey [32]byte
    56  		var hNonce [16]byte
    57  
    58  		copy(hNonce[:], nonce[:16])
    59  		copy(tmpKey[:], key)
    60  		HChaCha20(&tmpKey, &hNonce, &tmpKey)
    61  		copy(Nonce[8:], nonce[16:])
    62  		initialize(state, tmpKey[:], &Nonce)
    63  
    64  		// BUG(aead): A "good" compiler will remove this (optimizations)
    65  		//			  But using the provided key instead of tmpKey,
    66  		//			  will change the key (-> probably confuses users)
    67  		for i := range tmpKey {
    68  			tmpKey[i] = 0
    69  		}
    70  	default:
    71  		err = errInvalidNonce
    72  	}
    73  	return
    74  }
    75  
    76  // XORKeyStream crypts bytes from src to dst using the given nonce and key.
    77  // The length of the nonce determinds the version of ChaCha20:
    78  // - NonceSize:  ChaCha20/r with a 64 bit nonce and a 2^64 * 64 byte period.
    79  // - INonceSize: ChaCha20/r as defined in RFC 7539 and a 2^32 * 64 byte period.
    80  // - XNonceSize: XChaCha20/r with a 192 bit nonce and a 2^64 * 64 byte period.
    81  // The rounds argument specifies the number of rounds performed for keystream
    82  // generation - valid values are 8, 12 or 20. The src and dst may be the same slice
    83  // but otherwise should not overlap. If len(dst) < len(src) this function panics.
    84  // If the nonce is neither 64, 96 nor 192 bits long, this function panics.
    85  func XORKeyStream(dst, src, nonce, key []byte, rounds int) {
    86  	if rounds != 20 && rounds != 12 && rounds != 8 {
    87  		panic("chacha20/chacha: bad number of rounds")
    88  	}
    89  	if len(dst) < len(src) {
    90  		panic("chacha20/chacha: dst buffer is to small")
    91  	}
    92  	if len(nonce) == INonceSize && uint64(len(src)) > (1<<38) {
    93  		panic("chacha20/chacha: src is too large")
    94  	}
    95  
    96  	var block, state [64]byte
    97  	if err := setup(&state, nonce, key); err != nil {
    98  		panic(err)
    99  	}
   100  	xorKeyStream(dst, src, &block, &state, rounds)
   101  }
   102  
   103  // Cipher implements ChaCha20/r (XChaCha20/r) for a given number of rounds r.
   104  type Cipher struct {
   105  	state, block [64]byte
   106  	off          int
   107  	rounds       int // 20 for ChaCha20
   108  	noncesize    int
   109  }
   110  
   111  // NewCipher returns a new *chacha.Cipher implementing the ChaCha20/r or XChaCha20/r
   112  // (r = 8, 12 or 20) stream cipher. The nonce must be unique for one key for all time.
   113  // The length of the nonce determinds the version of ChaCha20:
   114  // - NonceSize:  ChaCha20/r with a 64 bit nonce and a 2^64 * 64 byte period.
   115  // - INonceSize: ChaCha20/r as defined in RFC 7539 and a 2^32 * 64 byte period.
   116  // - XNonceSize: XChaCha20/r with a 192 bit nonce and a 2^64 * 64 byte period.
   117  // If the nonce is neither 64, 96 nor 192 bits long, a non-nil error is returned.
   118  func NewCipher(nonce, key []byte, rounds int) (*Cipher, error) {
   119  	if rounds != 20 && rounds != 12 && rounds != 8 {
   120  		panic("chacha20/chacha: bad number of rounds")
   121  	}
   122  
   123  	c := new(Cipher)
   124  	if err := setup(&(c.state), nonce, key); err != nil {
   125  		return nil, err
   126  	}
   127  	c.rounds = rounds
   128  
   129  	if len(nonce) == INonceSize {
   130  		c.noncesize = INonceSize
   131  	} else {
   132  		c.noncesize = NonceSize
   133  	}
   134  
   135  	return c, nil
   136  }
   137  
   138  // XORKeyStream crypts bytes from src to dst. Src and dst may be the same slice
   139  // but otherwise should not overlap. If len(dst) < len(src) the function panics.
   140  func (c *Cipher) XORKeyStream(dst, src []byte) {
   141  	if len(dst) < len(src) {
   142  		panic("chacha20/chacha: dst buffer is to small")
   143  	}
   144  
   145  	if c.off > 0 {
   146  		n := len(c.block[c.off:])
   147  		if len(src) <= n {
   148  			for i, v := range src {
   149  				dst[i] = v ^ c.block[c.off]
   150  				c.off++
   151  			}
   152  			if c.off == 64 {
   153  				c.off = 0
   154  			}
   155  			return
   156  		}
   157  
   158  		for i, v := range c.block[c.off:] {
   159  			dst[i] = src[i] ^ v
   160  		}
   161  		src = src[n:]
   162  		dst = dst[n:]
   163  		c.off = 0
   164  	}
   165  
   166  	// check for counter overflow
   167  	blocksToXOR := len(src) / 64
   168  	if len(src)%64 != 0 {
   169  		blocksToXOR++
   170  	}
   171  	var overflow bool
   172  	if c.noncesize == INonceSize {
   173  		overflow = binary.LittleEndian.Uint32(c.state[48:]) > math.MaxUint32-uint32(blocksToXOR)
   174  	} else {
   175  		overflow = binary.LittleEndian.Uint64(c.state[48:]) > math.MaxUint64-uint64(blocksToXOR)
   176  	}
   177  	if overflow {
   178  		panic("chacha20/chacha: counter overflow")
   179  	}
   180  
   181  	c.off += xorKeyStream(dst, src, &(c.block), &(c.state), c.rounds)
   182  }
   183  
   184  // SetCounter skips ctr * 64 byte blocks. SetCounter(0) resets the cipher.
   185  // This function always skips the unused keystream of the current 64 byte block.
   186  func (c *Cipher) SetCounter(ctr uint64) {
   187  	if c.noncesize == INonceSize {
   188  		binary.LittleEndian.PutUint32(c.state[48:], uint32(ctr))
   189  	} else {
   190  		binary.LittleEndian.PutUint64(c.state[48:], ctr)
   191  	}
   192  	c.off = 0
   193  }
   194  
   195  // HChaCha20 generates 32 pseudo-random bytes from a 128 bit nonce and a 256 bit secret key.
   196  // It can be used as a key-derivation-function (KDF).
   197  func HChaCha20(out *[32]byte, nonce *[16]byte, key *[32]byte) { hChaCha20(out, nonce, key) }