Learn-by-doing blockchain: modular consensus (PoW/PoS), pluggable crypto (secp256k1 / Falcon PQC), P2P gossip, Merkle trees, blocks, state, and a tiny REST API.
β Star this repo if you like real, readable blockchain code!
Most tutorials hide the moving parts. This repo shows the whole path end-to-end:
- block and header structure
- merkle trees and block hashing
- PoW/PoS consensus (switchable)
- transaction signing & verification
- P2P sync & fork choice (longest chain)
- dynamic HTTP API for quick experiments
- unit tests for the core primitives
Youβll be able to run multiple nodes locally, mine blocks, send signed transactions, and watch chains sync.
# 1) Install
npm install
# 2) Run your first node (PoW + secp256k1)
npm start
# watch logs:
# [http] listening on :8080
# [p2p] listening on <random>
# [wallet] address: <your 40-hex address>
# 3) In another terminal, run a second node
npm start
# second node picks a new HTTP port (e.g., :8081) and connects
# 4) Mine an empty block on node A (instant, easy PoW target via /mine)
curl -X POST http://localhost:8080/mine
# 5) Check both nodes now show the same tip:
curl http://localhost:8080/latest
curl http://localhost:8081/latestIf both latest blocks match, congrats β you have a tiny blockchain cluster syncing locally.
src/
ββ consensus/ # PoW / PoS implementations
ββ core/ # block, chain, merkle, state
ββ crypto/ # Crypto adapter (secp256k1 or Falcon)
ββ network/ # P2P gossip + HTTP server
ββ tx/ # transaction model & verification
ββ wallet/ # wallet init & key persist
ββ index.js # entrypoint (starts P2P and HTTP)
test/ # Jest tests for core modules
- BlockHeader: links to previous block, commits to the Merkle root of txs, includes timestamp + PoW/PoS fields.
- Merkle Tree: deterministic hash of the tx list; order matters; last leaf duplicates when odd.
- PoW: mines a header hash under a target (nBits). We add
/minewith an easy target for instant demos. - PoS: selects a validator with probability proportional to stake (demo stakes are in-memory).
- Chain Validity: checks parent linkage, timestamps, consensus-specific validity, and per-tx signature/balance/nonce.
- P2P: peers gossip new blocks, request missing blocks, and can request a full chain on forks; longest chain wins.
# Proof-of-Work (default)
npm run start:pow
# Proof-of-Stake
npm run start:posor:
CONSENSUS_MODE=pow node src/index.js
CONSENSUS_MODE=pos node src/index.js# Classic ECDSA (secp256k1) β default
npm run start:secp
# Post-Quantum Falcon-512 (requires pqclean)
npm run start:falconor:
CRYPTO_MODE=falcon node src/index.jsYou can combine:
CRYPTO_MODE=falcon CONSENSUS_MODE=pos node src/index.jsEach node auto-picks a free port (8080, 8081, β¦).
GET /blocksβ entire chainGET /block/:indexβ block by indexGET /latestβ latest blockGET /state/:addressβ{ balance, nonce }POST /mineβ mine an empty block (PoW demo, instant)POST /txβ submit a signed transaction (see below)
curl -X POST http://localhost:8080/mine
curl http://localhost:8080/latest
curl http://localhost:8080/blocks | jq 'length'Transactions must be signed using the same crypto mode the node is running.
Weβll:
- read your local wallet,
- sign a tx in a one-liner Node script,
- POST it to the node.
When you start the node youβll see:
[wallet] address: 09c8186fc8073ee1728f9a2586a221b81d4f5c2a
export FROM=09c8186fc8073ee1728f9a2586a221b81d4f5c2a
export TO=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
node -e "(
async () => {
const fs = require('fs');
const CryptoAdapter = require('./src/crypto/cryptoAdapter');
const pk = fs.readFileSync('./src/wallet/private_key','utf8').trim();
const pub = fs.readFileSync('./src/wallet/public_key','utf8').trim();
const from = process.env.FROM;
const to = process.env.TO;
const amount = 1, fee = 0, nonce = 1;
const message = from + to + amount;
const signature = await CryptoAdapter.sign(message, pk);
const tx = { fromAddress: from, toAddress: to, amount, fee, nonce, publicKey: pub, signature };
console.log(JSON.stringify(tx));
}
)()" > /tmp/tx.jsoncurl -X POST http://localhost:8080/tx -H "Content-Type: application/json" --data-binary @/tmp/tx.jsonCheck balances:
curl http://localhost:8080/state/$FROM
curl http://localhost:8080/state/$TO- Terminal A:
npm start
# [http] :8080- Terminal B:
npm start
# [http] :8081
# [p2p] connected...- Mine on A:
curl -X POST http://localhost:8080/mine- Both tips match:
curl http://localhost:8080/latest
curl http://localhost:8081/latestnpm test
npm run test:watch
npm run test:covCovers:
- merkle root
- block hashing
- PoW & PoS validation
- tx signing + balance/nonce
- chain add/replace
CONSENSUS_MODEβ pow | posCRYPTO_MODEβ secp256k1 | falconHTTP_PORTβ preferred port (auto-picks next free otherwise)
- Port in use β set
HTTP_PORTor let auto-pick. - Peers not connecting β check firewall; discovery-swarm needs LAN.
- Nothing mines β POST
/minefor instant PoW demo. - Tx rejected β wrong nonce, insufficient balance, or crypto mode mismatch.
- Add coinbase reward to miner.
- Persist state in RocksDB.
- On-chain stakes for PoS.
- Implement a mempool.
- Difficulty retarget for PoW.
- Build a block explorer UI.
PRs, issues, and stars welcome π
- Fork
- Branch
feat/my-feature - Commit + Push
- PR π