Skip to content

Initialize ChaChaRng with arbitrary counter? #1369

@xpe

Description

@xpe

I don't see a way to initialize ChaChaRng with an arbitrary counter value.

I picked ChaCha because it is a great PRNG with a counter / "random access" / "fast forwarding". From Wikipedia:

[The ChaCha cipher is] built on a pseudorandom function based on add-rotate-XOR (ARX) operations — 32-bit addition, bitwise addition (XOR) and rotation operations. The core function maps a 256-bit key, a 64-bit nonce, and a 64-bit counter to a 512-bit block of the key stream [...]. This gives Salsa20 and ChaCha the unusual advantage that the user can efficiently seek to any position in the key stream in constant time.

Not seeing a way to initialize the counter with the current API seems like an oversight. Or perhaps I'm misunderstanding how to appropriately use the counter? If my request makes sense, I would be happy to help with a PR.

Here is one way to construct a ChaCha RNG:

impl ChaCha {
    #[inline(always)]
    pub fn new(key: &[u8; 32], nonce: &[u8]) -> Self {
        init_chacha(key, nonce)
    }
}

Below, you can see ctr_nonce corresponds to 4 32-bit words. In this implementation, the first word is the counter; the rest (3 words = 12 bytes) is the nonce. In the code below, the counter is fixed at 0.

dispatch_light128!(m, Mach, {
    fn init_chacha(key: &[u8; 32], nonce: &[u8]) -> ChaCha {
        let ctr_nonce = [
            0,
            if nonce.len() == 12 {
                read_u32le(&nonce[0..4])
            } else {
                0
            },
            read_u32le(&nonce[nonce.len() - 8..nonce.len() - 4]),
            read_u32le(&nonce[nonce.len() - 4..]),
        ];
        let key0: Mach::u32x4 = m.read_le(&key[..16]);
        let key1: Mach::u32x4 = m.read_le(&key[16..]);
        ChaCha {
            b: key0.into(),
            c: key1.into(),
            d: ctr_nonce.into(),
        }
    }
});

Also, for reference, from https://datatracker.ietf.org/doc/html/rfc8439 :

Note also that the original ChaCha had a 64-bit nonce and 64-bit
block count.  We have modified this here to be more consistent with
recommendations in [Section 3.2 of [RFC5116]](https://datatracker.ietf.org/doc/html/rfc5116#section-3.2).  This limits the use of
a single (key,nonce) combination to 2^32 blocks, or 256 GB, but that
is enough for most uses.  In cases where a single key is used by
multiple senders, it is important to make sure that they don't use
the same nonces.  This can be assured by partitioning the nonce space
so that the first 32 bits are unique per sender, while the other 64
bits come from a counter.

The ChaCha20 state is initialized as follows:

o  The first four words (0-3) are constants: 0x61707865, 0x3320646e,
   0x79622d32, 0x6b206574.

o  The next eight words (4-11) are taken from the 256-bit key by
   reading the bytes in little-endian order, in 4-byte chunks.

o  Word 12 is a block counter.  Since each block is 64-byte, a 32-bit
   word is enough for 256 gigabytes of data.

o  Words 13-15 are a nonce, which MUST not be repeated for the same
   key.  The 13th word is the first 32 bits of the input nonce taken
   as a little-endian integer, while the 15th word is the last 32
   bits.

    cccccccc  cccccccc  cccccccc  cccccccc
    kkkkkkkk  kkkkkkkk  kkkkkkkk  kkkkkkkk
    kkkkkkkk  kkkkkkkk  kkkkkkkk  kkkkkkkk
    bbbbbbbb  nnnnnnnn  nnnnnnnn  nnnnnnnn

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions