diff --git a/peer/internal/uprng/uprng.go b/peer/internal/uprng/uprng.go index ead5a4c78..f858d32a1 100644 --- a/peer/internal/uprng/uprng.go +++ b/peer/internal/uprng/uprng.go @@ -6,7 +6,9 @@ package uprng import ( cryptorand "crypto/rand" + "encoding/binary" "io" + "math/bits" "sync" "time" @@ -27,20 +29,42 @@ const ( maxCipherDuration = 20 * time.Second ) -var ( - key = make([]byte, chacha20.KeySize) - nonce = make([]byte, chacha20.NonceSize) -) +// nonce implements a 12-byte little endian counter suitable for use as an +// incrementing ChaCha20 nonce. +type nonce struct { + limbs [3]uint32 + bytes []byte +} + +func newNonce() *nonce { + return &nonce{bytes: make([]byte, chacha20.NonceSize)} +} + +func (n *nonce) inc() { + var carry uint32 + n.limbs[0], carry = bits.Add32(n.limbs[0], 1, carry) + n.limbs[1], carry = bits.Add32(n.limbs[1], 0, carry) + n.limbs[2], carry = bits.Add32(n.limbs[2], 0, carry) + n.limbs[0], _ = bits.Add32(n.limbs[0], 0, carry) + binary.LittleEndian.PutUint32(n.bytes[0:4], n.limbs[0]) + binary.LittleEndian.PutUint32(n.bytes[4:8], n.limbs[1]) + binary.LittleEndian.PutUint32(n.bytes[8:12], n.limbs[2]) +} type prng struct { cipher *chacha20.Cipher read int t time.Time + key []byte + nonce *nonce mu sync.Mutex } func newPRNG() *prng { - p := new(prng) + p := &prng{ + nonce: newNonce(), + key: make([]byte, chacha20.KeySize), + } p.seed() return p } @@ -49,20 +73,17 @@ func newPRNG() *prng { // cipher has been originally seeded. // Panics only during initial seeding if a crypto/rand read errors. func (p *prng) seed() { - _, err := cryptorand.Read(key) + _, err := cryptorand.Read(p.key) if err != nil && p.cipher == nil { panic(err) } if p.cipher != nil { - p.cipher.XORKeyStream(key, key) + p.cipher.XORKeyStream(p.key, p.key) } // never errors with correct key and nonce sizes - cipher, _ := chacha20.NewUnauthenticatedCipher(key, nonce) - - for i := range key { - key[i] = 0 - } + cipher, _ := chacha20.NewUnauthenticatedCipher(p.key, p.nonce.bytes) + p.nonce.inc() p.cipher = cipher p.read = 0