Skip to main content

Overview

The BLAKE3 inline provides an optimized implementation of the BLAKE3 hash function with 256-bit output. BLAKE3 is significantly faster than BLAKE2, SHA-1, SHA-2, and SHA-3 while maintaining security. The current implementation is optimized for inputs up to 64 bytes (single block).

API Reference

Blake3

Main hasher struct supporting both streaming and one-shot hashing.
pub struct Blake3 {
    // Internal state - users don't need to access directly
}

Methods

new() -> Self
Creates a new BLAKE3 hasher.
let mut hasher = Blake3::new();
update(&mut self, input: &[u8])
Writes data to the hasher incrementally. Panics if total input exceeds 64 bytes.
let mut hasher = Blake3::new();
hasher.update(b"hello ");
hasher.update(b"world");
finalize(self) -> [u8; 32]
Finalizes the hash and returns the 32-byte digest.
let hash = hasher.finalize();
digest(input: &[u8]) -> [u8; 32]
Computes BLAKE3 hash in one call. Input must be ≤64 bytes.
let hash = Blake3::digest(b"hello world");

AlignedHash32

8-byte aligned 32-byte hash/key type for efficient operations.
#[repr(align(8))]
pub struct AlignedHash32(pub [u8; 32]);

Methods

pub const fn new(bytes: [u8; 32]) -> Self;
pub const fn zeroed() -> Self;
pub fn as_bytes(&self) -> &[u8; 32];
pub fn as_bytes_mut(&mut self) -> &mut [u8; 32];

blake3_keyed64()

BLAKE3 keyed hash for 64-byte inputs (Merkle tree optimization).
pub fn blake3_keyed64(
    left: &AlignedHash32,
    right: &AlignedHash32,
    key: &mut AlignedHash32
)
This is equivalent to blake3::keyed_hash(key, left || right) but optimized for when left and right are in separate memory locations.

Usage Examples

Basic Hashing (≤64 bytes)

use blake3_inline::Blake3;

#[jolt::provable]
fn hash_short_data(data: &[u8]) -> [u8; 32] {
    assert!(data.len() <= 64);
    Blake3::digest(data)
}

Merkle Tree Parent Node

use blake3_inline::{blake3_keyed64, AlignedHash32, BLAKE3_IV};

#[jolt::provable]
fn hash_merkle_parent(left: [u8; 32], right: [u8; 32]) -> [u8; 32] {
    let left_aligned = AlignedHash32::new(left);
    let right_aligned = AlignedHash32::new(right);
    let mut iv = BLAKE3_IV;
    
    blake3_keyed64(&left_aligned, &right_aligned, &mut iv);
    iv.0
}

Streaming API (≤64 bytes total)

use blake3_inline::Blake3;

#[jolt::provable]
fn hash_fields(field1: &[u8], field2: &[u8]) -> [u8; 32] {
    let mut hasher = Blake3::new();
    hasher.update(field1);
    hasher.update(field2);
    hasher.finalize()
}

Implementation Details

Compression Function

BLAKE3 uses a ChaCha-like compression function:
  • State: 8 × 32-bit words (256 bits)
  • Message block: 16 × 32-bit words (512 bits)
  • Counter: 2 × 32-bit words (64 bits)
  • Flags: 1 × 32-bit word (32 bits)
  • Rounds: 7 rounds

Custom Instructions

  1. BLAKE3_COMPRESS (funct3=0x00, funct7=0x03): Standard compression
  2. BLAKE3_KEYED64 (funct3=0x01, funct7=0x03): Keyed 64-byte hash for Merkle trees

BLAKE3 Flags

pub const FLAG_CHUNK_START: u32 = 1;
pub const FLAG_CHUNK_END: u32 = 2;
pub const FLAG_PARENT: u32 = 4;      // For Merkle tree parent nodes
pub const FLAG_ROOT: u32 = 8;
pub const FLAG_KEYED_HASH: u32 = 16;

Initialization Vector

pub const IV: [u32; 8] = [
    0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
    0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
];

Message Scheduling

pub const MSG_SCHEDULE: [[usize; 16]; 7] = [
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
    [2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8],
    // ... (5 more rounds)
];

Constants

pub const INLINE_OPCODE: u32 = 0x0B;
pub const BLAKE3_FUNCT3: u32 = 0x00;
pub const BLAKE3_KEYED64_FUNCT3: u32 = 0x01;
pub const BLAKE3_FUNCT7: u32 = 0x03;

pub const NUM_ROUNDS: u8 = 7;
pub const CHAINING_VALUE_LEN: usize = 8;
pub const MSG_BLOCK_LEN: usize = 16;
pub const BLOCK_INPUT_SIZE_IN_BYTES: usize = 64;
pub const OUTPUT_SIZE_IN_BYTES: usize = 32;

Input Size Limitation

The current implementation only supports inputs up to 64 bytes (single block):
if len > BLOCK_INPUT_SIZE_IN_BYTES {
    panic!("Input too large: {len} bytes, max is {BLOCK_INPUT_SIZE_IN_BYTES}");
}
This is sufficient for:
  • Merkle tree nodes (2 × 32-byte hashes)
  • Small messages
  • Hash-based signatures
For larger inputs, use BLAKE2 or SHA-256 inlines instead.

Performance Characteristics

  • Block size: 64 bytes per compression
  • Output size: 32 bytes (256 bits)
  • Speed: Fastest among all hash inlines
  • Optimization: Specialized blake3_keyed64() for Merkle trees

Alignment Requirements

The AlignedHash32 type ensures 8-byte alignment for efficient 64-bit load/store operations:
#[repr(align(8))]
pub struct AlignedHash32(pub [u8; 32]);
This is critical for RISC-V performance.

Feature Flags

  • host: Enables reference implementation for host-side execution
    • Guest code: Compile WITHOUT this feature
    • Prover code: Compile WITH this feature

Comparison with Standard Implementation

When compared to the official blake3 crate:
  • Cycle count: ~10-50x reduction for single-block inputs
  • Proving time: Proportionally faster proof generation
  • Compatibility: Identical output to blake3::hash()

Source Code Location

jolt-inlines/blake3/
├── src/
│   ├── lib.rs          # Module definitions and constants
│   ├── sdk.rs          # Public API (Blake3, AlignedHash32)
│   ├── exec.rs         # Host-side reference implementation
│   └── sequence_builder.rs  # Instruction sequence generation
└── Cargo.toml

See Also

Build docs developers (and LLMs) love