Skip to content

chacha20: get ChaCha8 keystream blocks without applying them to a plaintext #424

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
nazar-pc opened this issue May 30, 2025 · 6 comments

Comments

@nazar-pc
Copy link

Chia Proof-of-Space implementation uses ChaCha8 cipher internally as PRNG instead of encrypting anything. This means that with current API it is necessary to apply it to zeroed vector first, which is an extra zeroing, memory copy and pointless XOR.

I'm wondering if it would be possible to somehow access internal keystream directly while doing less work, possibly by accepting &mut [MaybeUninit<u8>] as an argument.

@tarcieri
Copy link
Member

If you just want a PRNG you can use the rand_core-based PRNG API that’s in the latest prereleases.

It’s unlikely we’ll add an unsafe API for writing into uninitialized memory. This is a micro-optimization that’s often unnecessary because LLVM can elide zero-initialization if it can prove it will be overwritten by a subsequent write.

@newpavlov
Copy link
Member

You also could use the block-level trait directly. It still uses mutable references over blocks without MaybeUninit, but it's usually trivial for the compiler to elimination zero initialization of the buffer after inlining the methods.

@nazar-pc
Copy link
Author

I found write_keystream_blocks in public API, which is closer to what I need, but not quite it. It is also a bit awkward to use, especially because there is no ChaCha8::get_core_mut() or similar, and there is no ChaChaBlock type alias. I ended up with this:

struct PartialYs {
    buffer: Vec<GenericArray<u8, U64>>,
    len: usize,
}

impl Deref for PartialYs {
    type Target = [u8];

    #[inline(always)]
    fn deref(&self) -> &Self::Target {
        unsafe { slice::from_raw_parts(self.buffer.as_ptr().cast::<u8>(), self.len) }
    }
}


fn partial_ys<const K: u8>(seed: Seed) -> PartialYs {
    let output_len_bits = usize::from(K) * (1 << K);
    let output_len = output_len_bits.div_ceil(u8::BITS as usize);
    let mut output = vec![
        Block::<ChaChaCore<U4>>::default();
        output_len.div_ceil(ChaChaCore::<U4>::block_size())
    ];

    let key = Key::from(seed);
    let nonce = Nonce::default();

    let mut cipher = ChaChaCore::<U4>::new(&key, &nonce);

    cipher.write_keystream_blocks(&mut output);

    PartialYs {
        buffer: output,
        len: output_len,
    }
}

StreamCipherCoreWrapper::write_keystream() (alternative to ``StreamCipherCoreWrapper::apply_keystream()`) would have been nice here. But it didn't make a meaningful difference.

I'll give rand_chacha a try to, though not sure how optimized it is when compared to chacha20.

@newpavlov
Copy link
Member

newpavlov commented May 30, 2025

I'll give rand_chacha a try to, though not sure how optimized it is when compared to chacha20.

As @tarcieri wrote, the chacha20 crate implements the rand_core traits in v0.10 pre-releases (see the *Rng types). For example, you can use RngCore::fill_bytes on ChaCha8Rng.

@tarcieri
Copy link
Member

tarcieri commented May 30, 2025

As far as I am aware, chacha20 is faster than rand_chacha on all platforms, has optimized backends for more platforms, and has fewer (mandatory) dependencies, while supporting the same rand_core API.

See specifically https://docs.rs/chacha20/0.10.0-rc.0/chacha20/struct.ChaCha8Rng.html

@nazar-pc
Copy link
Author

I see, I thought it was the pre-release of rand crates initially.

chacha20::ChaCha8Rng doesn't seem to produce the same effective keystream though. I'm effectively doing something like this:

let mut chacha8 = ChaCha8Rng::from_seed(seed);
for _ in 0..N {
    let mut chunk = [0u8; 20];
    chacha8.fill_bytes(&mut chunk);

    // ...
}

And concatenation of chunks is not the original keystream. rand_chacha appears to have the same behavior.

@tarcieri tarcieri transferred this issue from RustCrypto/traits Jun 5, 2025
@tarcieri tarcieri changed the title Get ChaCha8 keystream blocks without applying them to a plaintext chacha20: get ChaCha8 keystream blocks without applying them to a plaintext Jun 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants