Storage Model
Substrate / Polkadot
Composed Storage Key Trie

Substrate uses a namespaced composed key:Twox128(pallet) ++ Twox128(storage) ++ hash(key)This gives every storage slot a structured, readable address — and allows iteration over all keys in a map.

Interactive: Build a Substrate Storage Key

Change the pallet name, storage name, and key to see how the composed storage key is built.

Substrate Key Formation
1Twox128("Balances")
Balances0x000000006ee02ec9000000006ee02ec9

TwoX128 hash of the pallet name "Balances". This namespaces all storage under this pallet. Fast, non-cryptographic hash — pallet names are trusted.

2Twox128("Account")
Account0x000000001d0c220d000000001d0c220d
3Blake2_128Concat("Alice")
Alice0x00000000152959e500000000152959e5...Alice
4Concatenate All Parts
Balances ++ Account ++ Alice0x000000006ee02ec9000000006ee02ec9000000...
Final DB Key:
0x000000006ee02ec9000000006ee02ec9000000...
Final Storage Key (hex)
Twox128(Balances):
0x000000006ee02ec9000000006ee02ec9
Twox128(Account):
0x000000001d0c220d000000001d0c220d
Blake2_128Concat(Alice):
0x00000000152959e500000000152959e5416c696365...

vs. Ethereum

ETH:key = keccak256(address)
— can't decode key back
SUB:key = hash(pallet) ++ hash(storage) ++ hash(key) ++ key
— can iterate all keys!

Real Substrate Key Example

substrate-key.txt
1// Substrate Storage Key Formation
2// TOTALLY different from Ethereum's keccak256(address)
3
4// Every storage item is addressed by:
5// Twox128(pallet_name) ++ Twox128(storage_name) ++ hash(key)
6
7// Example: Balances pallet, Account storage map, Alice's key
8
9Twox128("Balances") = 0x26aa394eea5630e07c48ae0c9558cef7
10Twox128("Account") = 0xb99d880ec681799c0cf30e8886371da9
11Blake2_128Concat(Alice_SS58)= 0xbe5ddb1579b72e84524fc29e78609e3c9fAlice_pubkey
12
13FINAL KEY = 0x26aa394eea5630e07c48ae0c9558cef7
14 ++ b99d880ec681799c0cf30e8886371da9
15 ++ be5ddb1579b72e84524fc29e78609e3c...
16
17// This is the key stored in the underlying trie / RocksDB
18// Length: 16 + 16 + (16 + 32) = 80 bytes (320 hex chars)

Storage Types: Value, Map, DoubleMap

pallet-storage.rsrust
1// Different storage types in Substrate pallets
2
3// 1. StorageValue — single value
4#[pallet::storage]
5pub type TotalIssuance<T> = StorageValue<_, u128>;
6// Key = Twox128("Balances") ++ Twox128("TotalIssuance")
7// No map key needed — it's a single global value
8
9// 2. StorageMap — key → value map
10#[pallet::storage]
11pub type Account<T: Config> = StorageMap<
12 _,
13 Blake2_128Concat, // hash function for the key
14 T::AccountId,
15 AccountData<T::Balance>,
16>;
17// Key = Twox128(pallet) ++ Twox128("Account") ++ Blake2_128Concat(account_id)
18
19// 3. StorageDoubleMap — key1 + key2 → value
20#[pallet::storage]
21pub type Locks<T: Config> = StorageDoubleMap<
22 _,
23 Blake2_128Concat, T::AccountId, // first key
24 Twox64Concat, LockIdentifier, // second key
25 BoundedVec<BalanceLock<T::Balance>, MaxLocks>,
26>;
27// Key = Twox128(pallet) ++ Twox128("Locks") ++ hash1(key1) ++ hash2(key2)

3 Hash Functions — Choose Wisely

hash-functions.txt
1// Substrate offers 3 hash functions for map keys
2// Choose based on security needs:
3
4// 1. Blake2_128Concat — SECURE (use for user-controlled keys)
5// key_hash = blake2_128(key) ++ key
6// - Resistant to HashDoS attacks
7// - Key is appended → can iterate and decode
8// - Use for: AccountId, user-provided data
9
10// 2. Twox64Concat — FAST (use for trusted keys)
11// key_hash = twox64(key) ++ key
12// - NOT secure against adversarial input
13// - Much faster than Blake2
14// - Use for: internal system keys (block numbers etc.)
15
16// 3. Identity — NO HASH (use for fixed-size trusted keys)
17// key_hash = key (no hashing at all)
18// - Fastest, but vulnerable to DoS
19// - Only for fixed-size, trusted, non-adversarial keys
20
21// Why Blake2_128Concat vs Ethereum's keccak256?
22// Ethereum: keccak256(key) → no way to reverse → can't enumerate
23// Substrate: blake2_128(key) ++ key → can iterate all keys in a map!
Blake2_128Concat
Security: High
Speed: Medium
User-controlled keys (AccountId, etc.)
Twox64Concat
Security: Low*
Speed: High
Trusted system keys (block numbers, etc.)
Identity
Security: None
Speed: Max
Fixed-size, trusted, non-adversarial keys

Substrate vs Ethereum — Key Differences

substrate-vs-ethereum.txt
1// Substrate Trie vs Ethereum MPT
2
3// ETHEREUM:
4key = keccak256(address) // single hash, 32 bytes
5// → Traverse 64 nibbles in 16-way trie
6// → RLP-encoded node stored as DB[keccak256(RLP(node))]
7// → 3 node types: branch, extension, leaf
8
9// SUBSTRATE:
10key = Twox128(pallet) ++ Twox128(storage) ++ hash(key)
11// → SAME trie traversal after key construction
12// → But different encoding (not RLP — custom codec SCALE)
13// → Optimized trie (more compact, fewer node types)
14
15// The KEY INSIGHT:
16// ┌─────────────────────────────────────────────────┐
17// │ BOTH end up in a trie stored as hash→node in DB│
18// │ Difference is HOW the key is constructed │
19// │ │
20// │ Ethereum: one hash → trie │
21// │ Substrate: namespaced hash → same trie │
22// └─────────────────────────────────────────────────┘

Account Data Structure

account-data.rsrust
1// Substrate AccountData (Balances pallet)
2// Stored at: Twox128("Balances")++Twox128("Account")++Blake2_128Concat(who)
3
4AccountData {
5 free: u128, // freely transferable balance
6 reserved: u128, // locked for staking/governance
7 frozen: u128, // frozen by governance/system
8 flags: ExtraFlags,
9}
10
11// Compare with Ethereum:
12// Ethereum account: { nonce, balance, storageRoot, codeHash }
13// Substrate account: { free, reserved, frozen, flags }
14// + nonce stored separately in System pallet:
15// Twox128("System") ++ Twox128("Account") ++ Blake2_128Concat(who)
🧩

Pallet Architecture

Unlike Ethereum's single flat state, Substrate organizes state by pallets. Each pallet owns its storage.

System pallet → nonce, block number, events
Balances pallet → free, reserved, frozen
Staking pallet → validators, delegations
EVM pallet → Ethereum-compatible contracts
🔄

Key Iteration — Major Advantage

Because Blake2_128Concat appends the raw key to the hash, you can scan a storage map and decode all keys:

for (key, value) in storage_map.iter() {
  // key = blake2_128(k) ++ k → extract k
  let account = decode(key[16..]);
}

How Composed Keys → Capabilities

Twox128(pallet) prefix

Complete namespace isolation. Pallets can never accidentally share storage keys. Add new pallets without collision risk.

Blake2_128Concat appends raw key

Can iterate all entries in a StorageMap — useful for migrations, queries. Ethereum (keccak256) can't do this.

⚠️
Structured keys add length overhead

Keys are 80+ bytes vs Ethereum's 32 bytes. More data per trie path.

Runtime upgrades with same storage layout

Substrate supports forkless runtime upgrades because storage keys are deterministic from pallet/storage names.