Blockchain Basics in Rust
9 examples to get you started with blockchain development in Rust - 7 basic and 2 intermediate.
Prerequisites
cargo new chain-playground && cd chain-playground
cargo add sha2 ed25519-dalek hex serde --features derive
cargo add tokio --features fullTooling: Examples target Rust 1.97.0 (edition 2024). On-chain targets may use older editions per SDK - follow each chain's template.
Basic Examples
1. Hash data with SHA-256
Block and transaction IDs start with cryptographic hashes.
use sha2::{Digest, Sha256};
fn hash_hex(data: &[u8]) -> String {
let digest = Sha256::digest(data);
hex::encode(digest)
}- SHA-256 is ubiquitous in Bitcoin-style merkle trees and content IDs.
- Always hash canonical byte encoding, not JSON with unstable key order.
- Display as hex for logs; store raw bytes on-chain when space matters.
Related: Applied Cryptography - primitive catalog
2. Generate an Ed25519 keypair
Modern chains use Ed25519 for fast signatures and small keys.
use ed25519_dalek::SigningKey;
use rand::rngs::OsRng;
fn new_keypair() -> SigningKey {
SigningKey::generate(&mut OsRng)
}SigningKeymust stay secret;VerifyingKeyis public.- Use OS RNG, never hard-coded seeds in production.
- Persist keys encrypted at rest - see wallets page.
Related: Wallets & Key Management
3. Sign and verify a message
Signatures prove authorization without revealing the private key.
use ed25519_dalek::{Signer, SigningKey, Verifier, VerifyingKey};
fn sign_verify(key: &SigningKey, msg: &[u8]) -> bool {
let sig = key.sign(msg);
let vk: VerifyingKey = key.verifying_key();
vk.verify(msg, &sig).is_ok()
}- Verify before acting on peer messages in P2P handlers.
- Include domain separation strings in
msgto prevent cross-protocol replay. - Constant-time verification matters on validators facing public internet.
4. Serialize with serde for deterministic configs
Chain configs and genesis files use structured serialization.
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct GenesisAccount {
pubkey: String,
balance: u64,
}- Prefer fixed-width integers (
u64) for token amounts in configs. - Document decimals off-chain; on-chain often uses smallest units only.
- Version genesis schema to migrate testnets safely.
5. Model an account balance with checked math
Overflow panics become exploits in token contracts.
fn transfer(balance: u64, amount: u64) -> Option<u64> {
balance.checked_sub(amount)
}checked_*returnsNoneon overflow instead of wrapping.- Never use
f64for token amounts in consensus code. - Match token program rules (fees, rent) in the same integer domain.
Related: Security & Auditing - integer bugs
6. Represent a simple transaction struct
Keep on-chain structs small and copy-friendly where programs require it.
#[derive(Clone, Debug)]
struct Tx {
from: [u8; 32],
to: [u8; 32],
lamports: u64,
}- Fixed arrays for pubkeys avoid heap allocation in hot paths.
- Separate wire format (Borsh/Anchor) from internal domain types when helpful.
- Validate all fields at the program entrypoint before mutating state.
Related: Solana Programs
7. Async JSON-RPC client sketch
Nodes expose HTTP/WebSocket APIs for chain interaction.
use reqwest::Client;
use serde_json::json;
async fn get_balance(client: &Client, url: &str, address: &str) -> reqwest::Result<serde_json::Value> {
let body = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "getBalance",
"params": [address]
});
let res = client.post(url).json(&body).send().await?;
res.json().await
}- Time out RPC calls; public endpoints rate-limit aggressively.
- Retry with backoff only for idempotent reads, not sends.
- Run validators and bots on Tokio with bounded concurrency.
Intermediate Examples
8. Merkle root over transaction hashes
Light clients verify inclusion with merkle proofs.
use sha2::{Digest, Sha256};
fn merkle_root(leaves: &[Vec<u8>]) -> [u8; 32] {
if leaves.is_empty() {
return [0u8; 32];
}
let mut layer: Vec<[u8; 32]> = leaves.iter().map(|l| Sha256::digest(l).into()).collect();
while layer.len() > 1 {
let mut next = Vec::new();
for chunk in layer.chunks(2) {
let mut h = Sha256::new();
h.update(chunk[0]);
h.update(chunk.get(1).unwrap_or(&chunk[0]));
next.push(h.finalize().into());
}
layer = next;
}
layer[0]
}- Duplicate last leaf when count is odd - schemes vary, document your rule.
- Proof verification is cheaper than re-executing all txs.
- Property-test root stability against reference implementations.
Related: Consensus & P2P
9. Feature-gated chain SDK dependency
Keep off-chain tooling separate from on-chain program crates.
[dependencies]
anchor-lang = { version = "0.30", optional = true }
[features]
on-chain = ["anchor-lang"]- On-chain builds target BPF/SBF with size and syscall limits.
- Off-chain integration tests use full Tokio and reqwest.
- One repo, two Cargo targets via workspaces - see Substrate page.
Related: Substrate & ink!
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.