Skip to main content

Overview

The SHA-256 inline provides an optimized implementation of the SHA-2 family hash function with 256-bit output. It offers significant performance improvements over standard SHA-256 implementations by using custom RISC-V instructions.

API Reference

Sha256

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

Methods

new() -> Self
Creates a new SHA-256 hasher.
let mut hasher = Sha256::new();
update(&mut self, input: &[u8])
Writes data to the hasher incrementally. Can be called multiple times.
let mut hasher = Sha256::new();
hasher.update(b"hello ");
hasher.update(b"world");
finalize(self) -> [u8; 32]
Finalizes the hash and returns the 32-byte digest. Consumes the hasher.
let hash = hasher.finalize();
digest(input: &[u8]) -> [u8; 32]
Computes SHA-256 hash in one call. Recommended for most use cases.
let hash = Sha256::digest(b"hello world");

Usage Examples

Basic Usage

use sha2_inline::Sha256;

#[jolt::provable]
fn verify_hash(data: &[u8], expected: [u8; 32]) -> bool {
    let hash = Sha256::digest(data);
    hash == expected
}

Streaming API

For large inputs or when data arrives in chunks:
use sha2_inline::Sha256;

#[jolt::provable]
fn hash_chunks(chunks: &[&[u8]]) -> [u8; 32] {
    let mut hasher = Sha256::new();
    for chunk in chunks {
        hasher.update(chunk);
    }
    hasher.finalize()
}

Merkle Tree Leaf Hashing

use sha2_inline::Sha256;

#[jolt::provable]
fn hash_leaf(index: u64, data: &[u8]) -> [u8; 32] {
    let mut hasher = Sha256::new();
    hasher.update(&index.to_le_bytes());
    hasher.update(data);
    hasher.finalize()
}

Implementation Details

Custom Instructions

The SHA-256 inline uses two custom RISC-V instructions:
  1. SHA256_COMPRESSION (funct3=0x00, funct7=0x00): Performs SHA-256 compression with existing state
  2. SHA256_COMPRESSION_INITIAL (funct3=0x01, funct7=0x00): Performs compression with initial IV constants

Memory Layout

  • Input block: 64 bytes (16 × 32-bit words)
  • State: 32 bytes (8 × 32-bit words)
  • Output: 32 bytes

Endianness Handling

The implementation automatically handles endianness:
  • On little-endian systems (RISC-V), uses swap_bytes() to convert to big-endian
  • On big-endian systems, data is used directly
The swap_bytes() function uses a custom virtual instruction on RISC-V for performance:
core::arch::asm!(
    ".insn i {opcode}, {funct3}, {r_inout}, {r_inout}, 0",
    opcode = const VIRTUAL_INSTRUCTION_TYPE_I_OPCODE,
    funct3 = const REV8W_FUNCT3,
    // ...
);

Performance Characteristics

  • Block processing: ~64 bytes per compression (512 bits)
  • Optimization: Initial block uses specialized instruction that embeds IV constants
  • Memory efficiency: Uses MaybeUninit for buffer allocation to reduce initialization overhead

Comparison with Standard Implementation

When compared to the Rust sha2 crate:
  • Cycle count: ~10-100x reduction in VM cycles
  • Proving time: Proportionally faster proof generation
  • Compatibility: Drop-in replacement with identical output

Feature Flags

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

Safety Considerations

The implementation uses unsafe for:
  • Inline assembly for custom RISC-V instructions
  • Raw pointer operations for zero-copy buffer access
  • MaybeUninit for performance optimization
All unsafe code is carefully reviewed and encapsulated within safe APIs.

Source Code Location

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

Constants

pub const INLINE_OPCODE: u32 = 0x0B;
pub const SHA256_FUNCT3: u32 = 0x00;
pub const SHA256_FUNCT7: u32 = 0x00;
pub const SHA256_NAME: &str = "SHA256_INLINE";

pub const SHA256_INIT_FUNCT3: u32 = 0x01;
pub const SHA256_INIT_FUNCT7: u32 = 0x00;
pub const SHA256_INIT_NAME: &str = "SHA256_INIT_INLINE";

See Also

Build docs developers (and LLMs) love