Skip to content

[RFC] Replace the BLAKE2b-based PRNG with ChaCha20 (ChaCha20Rng) - #425

Open
davidrusu wants to merge 5 commits into
masterfrom
rfc/blend-chacha20-prng
Open

[RFC] Replace the BLAKE2b-based PRNG with ChaCha20 (ChaCha20Rng)#425
davidrusu wants to merge 5 commits into
masterfrom
rfc/blend-chacha20-prng

Conversation

@davidrusu

@davidrusu davidrusu commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

[RFC] Replace the BLAKE2b-Based PRNG with ChaCha20 (ChaCha20Rng)

Authors: David Rusu
Approvals (research):
Approvals (engineering):


Reviewer Orientation

Read Motivation first.

# Priority Document / Change What to look for
1 Critical Start here: common-cryptographic-components.md, PRNG construction replaced (Details §1) the ChaCha20 keystream definition; seed narrowing from 64 to 32 bytes
2 Critical message-encapsulation.md, CSPRBG reseated on ChaCha20 (Details §2) every keystream (E_k/D_k, fillers, header randomization) changes value; zero-nonce safety
3 High blend-protocol.md, CSPRNG notation and Proof of Selection (Details §3) m_i derivation changes value; little-endian and modulo-bias analysis unaffected

Status tracker

  • 🚧 Raw (make sure that all below is completed)
    • Template applied
    • Authors filled in
    • Authors agree on the RFC content
  • 📘 Draft (make sure that all below is completed)
    • All dependent specifications added
    • Specifications to deprecate added, if applicable
    • Specifications to retire added, if applicable
    • Research Lead assigned, or Project Lead assigned if the Research Lead is an author
    • Relevant Research Domain Experts assigned (cannot be authors)
  • ⚙️ Verified (make sure that all below is completed)
    • Researchers’ comments addressed
    • All logical changes documented
    • All Research reviewers approve the latest version
    • Engineering Lead assigned
    • Relevant Engineering Domain Experts assigned
  • 🔀 Merged (make sure that all below is completed)
    • Engineers’ comments addressed
    • Every change added to the change log
    • All Engineering reviewers approve the latest version
    • Specification version numbers assigned
    • Implementation reviewed and merged
    • Branch updated to master and all conflicts resolved
    • PR merged

Change log

Revision Description Date
v1 Initial PR description 2026-08-28
v2 Seed is exactly 32 bytes; truncation of digests is explicit at the protocol level 2026-08-28
v3 Seed hashes re-parameterized to BLAKE2b-256; no truncation anywhere 2026-08-28

Motivation

All deterministic randomness in the Blend protocol and Message Encapsulation comes from a home-grown construction: BLAKE2b in counter mode over a 64-byte seed. Its dominant use is as an encryption keystream: E_k(x) = CSPRBG(k) ⊕ x protects blend payloads and headers, making the PRNG a de facto stream cipher.

No weakness is known, but the construction is bespoke: no dedicated cryptanalysis as a stream cipher, no published test vectors, and every implementation must hand-roll it. ChaCha20 is a standardized, extensively analyzed stream cipher with audited implementations in every mainstream language (ChaCha20Rng from rand_chacha in Rust). Switching removes a bespoke construction from the protocol's trusted surface at equal or better performance.

Proposal

Replace the BLAKE2b-Based PRNG Construction with the ChaCha20 keystream:

CSPRBG(seed) = ChaCha20Keystream(key = seed, nonce = 0, initial_counter = 0)

ChaCha20 is used in Bernstein's original variant (20 rounds, 256-bit key, 64-bit nonce, 64-bit block counter). This is byte-for-byte the output of rand_chacha::ChaCha20Rng::from_seed(key) with the default stream identifier. Consumers take the first n bytes (or k bits) of the keystream, as today.

All call-site formulas are unchanged as written, but every produced value changes with the keystream. The only structural change is at the seeding boundary: the construction takes exactly a 32-byte seed, and the four domain-separated seed hashes (H_N/H_I/H_b/H_P) are re-parameterized from BLAKE2b-512 to BLAKE2b-256, so the digest is the seed and no truncation exists anywhere.

This PR applies the change to Common Cryptographic Components, Message Encapsulation, and the Blend Protocol.

Discussion

Security

  • Fitness for purpose. XOR-with-keystream is exactly the ChaCha20 cipher's intended use and the object of its cryptanalysis; BLAKE2b-in-counter-mode is an improvised cipher resting on general PRF assumptions.
  • Zero nonce is safe here. A fixed nonce requires that no key generate two different streams. The protocol already guarantees this: every keystream is keyed by a domain-separated hash of a per-encapsulation shared secret (see the keystream-uniqueness note in Message Encapsulation). The argument that makes the current construction safe makes the fixed-nonce keystream safe.
  • Seed narrowing (64 → 32 bytes). A 256-bit key matches the security level targeted elsewhere in the protocol. The seed hashes H_N/H_I/H_b/H_P are re-parameterized to 32-byte output, since seeding the CSPRBG is their only use: the digest is the seed, with no truncation step to get wrong. Note BLAKE2b-256 is not a truncated BLAKE2b-512 (the digest length is part of BLAKE2b's parameter block), so this changes derived values; the RFC is wire-value-breaking regardless.

Alternatives considered

  • Status quo. Not broken, but carries the assurance and implementation costs above for no benefit.
  • IETF ChaCha20 (RFC 8439). Its 32-bit counter caps a keystream at 256 GiB, and it does not match ChaCha20Rng's default construction. The DJB variant makes "use ChaCha20Rng off the shelf" a conforming implementation.
  • XChaCha20. The extended nonce buys nothing with the nonce fixed at zero.

Compatibility and rollout

Wire-value-breaking with an unchanged wire format: sizes, layouts, and serialization are identical, but every keystream, filler, randomized header, and Proof of Selection index takes a different value. Old and new nodes cannot interoperate, so the change must ship as a coordinated protocol version bump (or before any deployed network exists).

Ancillary properties

  • The existing CSPRBG()_8 / little-endian conventions apply unchanged, and the modulo-bias analysis in the Blend Protocol is unaffected.
  • Performance is equal or better (both are ARX designs; ChaCha20 skips per-block hash finalization and ChaCha20Rng ships SIMD backends), and the keystream is cheaply seekable.

Details

1. Common Cryptographic Components: replace the PRNG construction

The "BLAKE2b-Based PRNG Construction" section becomes "ChaCha20-Based PRNG Construction":

-PRNG(seed, i) = BLAKE2b(seed || encode_u64(i), out_len=64)
+PRNG(seed) = ChaCha20Keystream(key = seed, nonce = 0, initial_counter = 0)

The new section specifies:

  • ChaCha20 in its original variant: 20 rounds, 256-bit key, 64-bit nonce (fixed to zero), 64-bit block counter (starting at zero).
  • The seed is exactly 32 bytes, used directly as the key; deriving it from longer material (e.g., a 64-byte BLAKE2b digest) is an explicit protocol-level truncation.
  • Byte-for-byte equal to the output of ChaCha20Rng (rand_chacha) with the default stream identifier.
  • Output rules unchanged: first n bytes; for k bits, truncate the last byte.
  • Normative requirement, carried over and made explicit: a seed MUST NOT be reused across generation contexts; seed uniqueness and domain separation remain protocol-level concerns.
  • Interoperability note: an IETF ChaCha20 (RFC 8439) with a zero nonce produces an identical keystream for the first 2^32 blocks (256 GiB), far beyond any use in the protocol, so implementations may use either variant. This lets e.g. the Nim client reuse BearSSL's br_chacha20_ct_run already in its dependency tree.

The recommendation table is updated:

-| General Hashing & PRNG | [BLAKE2b](#blake2bgeneral-purpose-hashing) |
+| General Hashing | [BLAKE2b](#blake2bgeneral-purpose-hashing) |
+| PRNG & Keystreams | [ChaCha20](#chacha20-based-prng-construction) |

2. Message Encapsulation: CSPRBG notation and pseudocode

The CSPRBG() / CSPRBG()_x definitions point at the new construction and the reference pseudocode becomes:

 def pseudo_random(domain: bytes, key: bytes, size: int) -> bytes:
-    rand = BlakeRng.from_seed(hashds(domain, key)).generate(size)
+    rand = ChaCha20Rng.from_seed(hashds(domain, key)).generate(size)
     assert len(rand) == size
     return rand

The hashds reference implementations of H_N/H_I/H_b/H_P change from Blake2b.hash512 to Blake2b.hash256; their 32-byte digest is the CSPRBG seed.

The E_k/D_k, filler (r_{t,1..4}), header randomization, and node selection (l_i) formulas are textually unchanged. A note points at the interoperability condition (all CSPRBG outputs are message-sized, so IETF ChaCha20 with a zero nonce is compatible).

3. Blend Protocol: CSPRNG notation and Proof of Selection

  • Notation: CSPRNG() now links to the ChaCha20-Based PRNG Construction.
  • Proof of Selection: m_i = CSPRNG(H_N(ρ))_8 mod N is unchanged as written; the 8-byte output is now the first 8 bytes of the keystream seeded with H_N(ρ)[0..32], still little-endian.

Chores

  • Renamed remaining "BLAKE2b-Based PRNG" references and updated the section anchor and inbound links.
  • Updated the modulo-bias parenthetical in the Blend Protocol to name the ChaCha20 keystream.
  • Added revision-history rows to all three specifications.

Implementation

  • Implement CSPRBG on ChaCha20Rng (rand_chacha), seeded with the first 32 bytes of the domain-separated BLAKE2b digest, replacing BlakeRng
  • Update Proof of Selection node-index derivation (m_i) and its verification
  • Update message encapsulation/decapsulation (payload and header keystreams, fillers, header randomization)
  • Add cross-implementation test vectors: CSPRBG outputs for known seeds, encapsulate/decapsulate round trips, m_i selection vectors
  • Remove the BlakeRng implementation once no call sites remain
  • Verify the implementation matches this specification

Affected Specifications

Specification Status New version Note
Common Cryptographic Components Modified 1.1.0 PRNG construction replaced; recommendation table updated
Message Encapsulation Modified 1.1.0 CSPRBG reseated on ChaCha20; all keystream values change
Blend Protocol Modified 1.3.0 CSPRNG notation and Proof of Selection updated
Proof of Quota Unchanged n/a Source of ρ; listed for reviewer attention to confirm no PRNG dependency

🤖 Generated with Claude Code

davidrusu and others added 2 commits August 28, 2026 02:11
…0Rng)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Applies the RFC to the specifications and moves the RFC text to the
PR description, following the convention of #365/#375:

- common-cryptographic-components: PRNG construction replaced with the
  ChaCha20 keystream (DJB variant, zero nonce), recommendation table split
- message-encapsulation: CSPRBG reseated on ChaCha20Rng, seed truncated
  to 32 bytes at the CSPRBG boundary
- blend-protocol: CSPRNG notation and modulo-bias parenthetical updated

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@davidrusu
davidrusu marked this pull request as draft August 27, 2026 21:03
With a zero nonce the DJB and RFC 8439 variants produce identical
keystreams for the first 2^32 blocks, so implementations without a
ChaCha20Rng-compatible library (e.g. the Nim client via BearSSL's
br_chacha20_ct_run) can use any IETF ChaCha20 as-is.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@davidrusu

Copy link
Copy Markdown
Contributor Author

Implementation PR: logos-blockchain/logos-blockchain#3435

Comment on lines +123 to +124
- Implementations MAY use the IETF variant of ChaCha20 ([RFC 8439](https://datatracker.ietf.org/doc/html/rfc8439): 32-bit block counter, 96-bit nonce) with the nonce set to zero. With a zero nonce the two variants produce identical keystreams for the first $`2^{32}`$ blocks (256 GiB) of output: the original variant's upper counter word is zero in that range and coincides with the IETF variant's zero nonce words.
- Every use of this construction in the Logos Blockchain generates far less than $`2^{32}`$ blocks per seed, so any RFC 8439 implementation with a zero nonce and an initial counter of zero is byte-for-byte compatible with the definition above.

@davidrusu davidrusu Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This note is here for Nim where the IETF variant is the dominant version in the ecosystem (The non-IETF variant is the popular form in rust)

davidrusu and others added 2 commits August 28, 2026 18:10
The ChaCha20 construction no longer truncates internally: it takes
exactly a 32-byte seed, and each protocol states once, normatively,
that CSPRBG(H(x)) means seeding with the first 32 bytes of the digest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The four domain-separated hash functions existed solely to seed the
CSPRBG, so their 64-byte digests were always immediately halved. With
32-byte output the digest is the seed and no truncation exists
anywhere. Note BLAKE2b-256 is not truncated BLAKE2b-512 (digest length
is in the parameter block), so derived values change; the RFC is
already wire-value-breaking.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
davidrusu added a commit to logos-blockchain/logos-blockchain that referenced this pull request Aug 28, 2026
Implements the RFC v3 change (logos-co/logos-lips#425): the
domain-separated seed hash is re-parameterized from BLAKE2b-512 to
BLAKE2b-256, so the digest is the ChaCha20 seed and no truncation
exists. The now-unused blake2b512 helper is removed. BLAKE2b-256 is
not truncated BLAKE2b-512 (digest length is in the parameter block),
so all keystream-derived values change: codec fixtures regenerated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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

Successfully merging this pull request may close these issues.

1 participant