Nstrike — Blockchain légère (MVP)
📖 Description
Nstrike est une implémentation from scratch d'une Blockchain de type Ethereum, écrite entièrement en C++17 moderne sans aucune bibliothèque externe (ni OpenSSL, ni Boost, ni secp256k1). Le projet vise à démontrer le fonctionnement interne d'une Blockchain complète : cryptographie, machines virtuelles, consensus Proof-of-Work, comptes, jetons ERC-20, RPC JSON et CLI.
Auteur : Martial Zinsou
Version : 0.1.0 (MVP)
Licence : MIT
Langage : C++17 (sans dépendance externe)
Fonctionnalités principales
| Composant | Description |
|---|---|
| Cryptographie | SHA-256, Keccak-256 (permutation Keccak-f[1600]), secp256k1 (ECDSA, RFC 6979, ecrecover), adresses 20 octets style Ethereum |
| Arithmétique 256 bits |
u256 avec addition/soustraction modulaire, multiplication de Montgomery, inversion modulaire (Fermat), exponentiation modulaire, division exacte |
| Comptes & État | Nonce, solde, code contrat, storage (map clé→valeur), racine d'état canonique (Keccak cumulatif) |
| Transactions | Sérialisation canonique (magic + big-endian), signature ECDSA + recovery ID, vérification expéditeur (ecrecover), gas |
| Mini-VM (NVM) | Interpréteur pile 32 octets, opcodes style EVM (arithmétique, sauts, storage, calldata, Keccak, résultat), assembleur avec labels & PUSH_SEL
|
| Jeton ERC-20 | Template compilé en NVM : totalSupply, balanceOf, transfer, approve, allowance, transferFrom, name, symbol, decimals
|
| Blockchain & Consensus | Blocs (en-tête + txs), PoW léger (zéros de tête Keccak double), difficulté ajustable (fenêtre 24 blocs), récompense 50 NST + halving 210 000 blocs, mempool |
| RPC JSON 2.0 |
getbalance, sendtx, getblock, getblockcount, getgasprice, mine, chainstatus, deploytoken, callcontract
|
| CLI |
account new/list, address, balance, send, token create/transfer/balance, mine, chain, rpc
|
🏗️ Architecture
Nstrike/
├── src/
│ ├── config.hpp # Constantes du protocole
│ ├── common.hpp/.cpp # Types de base, hex, u256 (Montgomery), aléa
│ ├── sha256.hpp/.cpp # SHA-256 from scratch
│ ├── keccak.hpp/.cpp # Keccak-256 / SHA3-256 + Keccak-f[1600]
│ ├── crypto.hpp/.cpp # secp256k1, ECDSA, clés, adresses
│ ├── json.hpp/.cpp # Parseur/sérialiseur JSON minimal (RFC 8259)
│ ├── state.hpp/.cpp # Comptes, storage, racine d'état
│ ├── tx.hpp/.cpp # Transaction, signature, hash, sérialisation
│ ├── vm.hpp/.cpp # Mini-VM NVM, assembleur, template ERC-20
│ ├── chain.hpp/.cpp # Blocs, PoW, difficulté, récompense, mempool
│ ├── rpc.hpp/.cpp # Serveur JSON-RPC 2.0 (stdio)
│ └── main.cpp # CLI (wallet, envoi, minage, jetons, RPC)
├── tests/
│ └── test_main.cpp # Tests unitaires (vecteurs + intégration)
├── assets/
│ └── logo.svg # Logo Nstrike (style Microsoft)
├── Makefile # Build (make, make test, make run, make clean)
└── README.md # Ce fichier
⚙️ Prérequis
- macOS / Linux (testé sur macOS ARM64 & x86_64, Linux x86_64)
- Clang ≥ 10 ou GCC ≥ 9 (support C++17 complet)
make
⚠️ Aucune dépendance externe (OpenSSL, Boost, etc.) n'est requise.
🚀 Installation & Build
# Cloner le dépôt
git clone https://github.com/<votre-utilisateur>/Nstrike.git
cd Nstrike
# Compiler (binaire + tests)
make # → build/nstrike (CLI)
make test # → lance la suite de tests (32 vérifications)
# Nettoyer
make clean
Résultat attendu des tests
[ OK ] sha256_vectors
[ OK ] keccak256_vectors
[ OK ] u256_basics
[ OK ] u256_modular
[ OK ] ecc_basics
[ OK ] ecdsa_roundtrip
[ OK ] ecdsa_many
[ OK ] hex_roundtrip
8 test(s), 32 vérification(s), 0 échec(s)
💻 Utilisation (CLI)
Portefeuille
# Créer un nouveau compte (clé privée + adresse)
./build/nstrike account new
# → Nouveau compte : 0xabc123...
# Lister les comptes du wallet (~/.nstrike/wallet.json)
./build/nstrike account list
Minage (Proof-of-Work)
# Miner 1 bloc pour l'adresse donnée (récompense 50 NST + frais)
./build/nstrike mine 0x3ab19ecdaea8a14f4bb2a6f07fa4801e40be7c1f 1
# Miner 5 blocs
./build/nstrike mine 0x3ab19... 5
⚠️ Limitation MVP : la chaîne n'est pas persistée sur disque. Chaque invocation CLI crée une nouvelle chaîne en mémoire. Pour un minage réel, utilisez le serveur RPC (voir ci-dessous) ou intégrez la persistance (RocksDB, LevelDB, etc.).
Solde
./build/nstrike balance 0x3ab19ecdaea8a14f4bb2a6f07fa4801e40be7c1f
Transfert NST
# <privHex> <dest> <montant en nwei> [gasPrice en nwei]
./build/nstrike send 0x2e066... 0xabc123... 0x0de0b6b3a7640000 0x1
Jeton ERC-20
# Déployer un jeton (créateur doit avoir un compte dans le wallet)
./build/nstrike token create 0x3ab19... "MonToken" "MTK" 18 0x56bc75e2d63100000
# Transférer des jetons
./build/nstrike token transfer 0x3ab19... 0xTokenAddr 0xDestAddr 0x1000
# Consulter le solde d'un jeton
./build/nstrike token balance 0xTokenAddr 0xUserAddr
État de la Blockchain
./build/nstrike chain
Serveur JSON-RPC (stdio)
# Lance le serveur RPC sur l'entrée/sortie standard (Ctrl-D pour quitter)
./build/nstrike rpc
Exemple de requête RPC :
{"jsonrpc":"2.0","method":"getbalance","params":{"address":"0x3ab19..."},"id":1}
Réponse :
{"jsonrpc":"2.0","result":"0x2b5e3af16b1880000","id":1}
📡 API JSON-RPC 2.0
| Méthode | Paramètres | Description |
|---|---|---|
getbalance |
{"address": "0x..."} |
Solde NST (nwei) |
sendtx |
{"tx": "0x..."} |
Envoie une transaction signée (mempool) |
getblock |
{"height": 0} |
Infos bloc (hash, hauteur, timestamp, miner, nb tx) |
getblockcount |
{} |
Hauteur actuelle |
getgasprice |
{} |
Prix de gas suggéré (1 nwei) |
mine |
{"miner":"0x...","count":1} |
Mine count blocs |
chainstatus |
{} |
Hauteur, difficulté, mempool, stateRoot |
deploytoken |
{"creator":"0x...","nonce":0,"name":"...","symbol":"...","decimals":18,"supply":"0x..."} |
Déploie un ERC-20 |
callcontract |
{"contract":"0x...","data":"0x...","gas":50000,"from":"0x..."} |
Appel lecture seule (VM) |
🧪 Tests
make test
La suite vérifie :
- Vecteurs officiels SHA-256 & Keccak-256 (FIPS 202 / NIST)
- Permutation Keccak-f1600
- Arithmétique
u256: add/sub/mul/div/mod/inv/pow modulaire - secp256k1 : multiplication scalaire, addition, doublage, ECDSA (signature, vérification, récupération)
- Round-trip hex/JSON
- ERC-20 : déploiement,
transfer,balanceOf,approve,allowance,transferFrom
📚 Wiki GitHub (Structure recommandée)
Après avoir poussé sur GitHub, créez les pages Wiki suivantes via l'onglet Wiki du dépôt :
1. Home — Vue d'ensemble
- Résumé du projet, objectifs, public cible (éducation, recherche, prototypage)
- Diagramme d'architecture (Mermaid)
2. Cryptography — Détails crypto
- SHA-256 : constantes, compression, padding
- Keccak-256 : taux 136, domaine 0x01, permutation f1600, vecteurs de test
- secp256k1 : paramètres (p, n, G), coordonnées affines & Jacobien, formules d'addition/doublage
- ECDSA : RFC 6979 (k déterministe HMAC-SHA256), récupération
recid(0..3) - Adresses :
keccak256(pub[1..64])[12..32]
3. U256 Arithmetic — Arithmétique 256 bits
- Représentation little-endian 4×u64
- Addition/soustraction modulaire (sans retenue haute)
- Multiplication de Montgomery : REDC (CIOS),
R = 2^256,m' = -p⁻¹ mod 2^64 - Inversion modulaire :
a^(p-2) mod p(exponentiation binaire gauche→droite) - Division exacte (algorithme shift-and-subtract 256 itérations)
4. Virtual Machine (NVM) — Mini-VM
- Jeu d'opcodes (table complète avec coûts de gas)
- Pile de mots 32 octets (max 1023)
- Calldata :
CALLDATALOAD(offset),CALLDATASIZE - Storage :
SLOAD(key),SSTORE(key,value)— clé = mot 32 octets - Keccak :
KECCAKpop a,b → push keccak(a||b) — utilisé pour clés de mapping - Sauts :
JUMP(dest),JUMPI(dest,cond)—destdoit êtreJUMPDEST - Résultat :
RESULT(n)→ sortienmots - Assembler : labels
L_x:,PUSH1..PUSH32,PUSH_SEL "sig",JUMP L_x/JUMPI L_x(auto PUSH2)
5. ERC-20 Template — Contrat jeton
- Layout storage :
- slot 0 :
totalSupply - slot 1 :
name(ASCII left-aligned 32 octets) - slot 2 :
symbol - slot 3 :
decimals(u8 dans LSB) - slot 4 :
balances[addr]→ clé =keccak(addr||word(4)) - slot 5 :
allowance[owner][spender]→ clé =keccak(spender||keccak(owner||5))
- slot 0 :
- Dispatch par selector (premiers 4 octets de
keccak(sig)alignés à gauche) - Fonctions :
totalSupply,name,symbol,decimals,balanceOf,transfer,approve,transferFrom,allowance
6. Chain & Consensus — Chaîne & consensus
- En-tête : version, hauteur, timestamp, difficulté (bits), prevHash, stateRoot, txRoot, miner, nonce
- Hachage bloc : Keccak(sérialisation big-endian)
- PoW :
Keccak(Keccak(header))doit avoirdifficultyzéros de tête (MSB first) - Difficulté : fenêtre 24 blocs, cible 15 s/bloc
- Si temps réel > 3× attendu → difficulté –1
- Si temps réel < 1/3 attendu → difficulté +1
- Bornes [16, 32] bits
- Récompense :
BASE_REWARD = 50 NST = 5×10¹⁹ nwei, halving tous les 210 000 blocs (shrBits(1)itératif) - Mempool : validation complète (
runTxsur copie d'état) avant insertion - Application bloc : exécution séquentielle, remboursement gas non consommé, frais au mineur, stateRoot final
7. CLI Reference — Référence CLI
- Tableau complet commandes/sous-commandes/arguments
- Exemples copiables
- Format wallet JSON (
~/.nstrike/wallet.json)
8. RPC Specification — Spécification RPC
- Format JSON-RPC 2.0 (request/response, erreurs standard)
- Liste exhaustive méthodes, paramètres, codes d'erreur personnalisés (-32000 tx invalide)
9. Limitations & Roadmap — Limitations & feuille de route
- Pas de persistance : état perdu à chaque processus (MVP)
- Pas de P2P : pas de réseau, pas de synchronisation
- Gas simplifié : coûts fixes, pas de schedule EIP-1559
- Pas de receipts / logs : pas d'indexation événements
- Feuille de route : persistance (RocksDB), P2P (libp2p), sync, receipts, métriques, tests fuzzing
10. Contributing — Contribuer
- Style : C++17,
-Wall -Wextra, pas de dépendances - Tests : ajouter cas dans
tests/test_main.cpp - PR : CI (GitHub Actions)
make testobligatoire
🤝 Contribution
Les contributions sont les bienvenues ! Merci de :
- Forker le dépôt
- Créer une branche
feature/ma-fonctionnalite - Ajouter des tests dans
tests/test_main.cpp -
make testdoit passer - Ouvrir une Pull Request
📄 Licence
Ce projet est sous licence MIT — voir le fichier LICENSE pour les détails.
🙏 Remerciements
- XKCP (Keccak Code Package) pour les vecteurs de test officiels
- NIST FIPS 202 (SHA-3 / Keccak)
- Standards Efficient Cryptography Group (SECG) — secp256k1
- Ethereum Yellow Paper — modèle de référence comptes/gas/VM
- RFC 6979 — ECDSA déterministe
Note : Ce MVP est conçu à des fins éducatives et de prototypage. Il n'est pas destiné à la production (absence de persistance, P2P, audit de sécurité, etc.).
Chain & Consensus — Blocs, PoW, Difficulté, Récompense, Mempool
Module :
src/chain.hpp/.cpp
📘 About
Description : Structure de bloc avec header (version, height, timestamp, difficultyBits, prevHash, stateRoot, txRoot, miner, nonce) et body (vector). Algorithme PoW : Keccak256(double header serialization), validation par difficultéBits zéros de tête. Ajustement de difficulté tous les 24 blocs (fenêtre 24 * 15s). Récompense de base 50 NST avec halving tous les 210000 blocs. Mempool avec validation nonce, signature, solde, gas intrinsèque. Exécution séquentielle sur copie d'état avant commit bloc.
Tags : #blockchain #pow #difficulty #reward #halving #mempool #consensus #block #mine
Auteur : Martial Zinsou
🧱 Structure de Bloc
BlockHeader
| Champ | Type | Description |
|---|---|---|
version |
uint32_t |
Version du protocole (1) |
height |
uint32_t |
Hauteur du bloc (0 = genesis) |
timestamp |
uint64_t |
Unix timestamp (secondes) |
difficultyBits |
u256 |
Bits de zéros de tête requis (MSB first) |
prevHash |
fix32 |
Hash du bloc précédent |
stateRoot |
fix32 |
Racine d'état après exécution des txs |
txRoot |
fix32 |
Racine des transactions (Keccak cumulatif) |
miner |
fix20 |
Adresse du mineur (bénéficiaire récompense) |
nonce |
u256 |
Preuve de travail (cherché par minage) |
Block
struct Block {
BlockHeader header;
std::vector<Transaction> txs;
bytes serialize() const;
static Block deserialize(const bytes& b);
};
Sérialisation (Big-endian)
version(4) | height(4) | timestamp(8) | difficulty(32)
| prevHash(32) | stateRoot(32) | txRoot(32)
| miner(20) | nonce(32) | ntx(4) | [tx_len(4) + tx_data]*
⛏️ Proof-of-Work (PoW Léger)
Algorithme
h1 = Keccak256(header_serialisé)-
h2 = Keccak256(h1)(double hachage style Bitcoin) -
Valide si
h2a au moinsdifficultyBitszéros de tête (MSB first)
Vérification (validPoW)
bool validPoW() const {
fix32 h = Keccak256(Keccak256(header_serialized));
for i in 0..difficultyBits-1:
if bit(h, i) != 0: return false;
return true;
}
Minage (mineBlocks)
- Pré-calcule la partie fixe de l'en-tête (sans nonce)
- Boucle
nonce = 0,1,2...jusqu'àvalidPoW() - Taux ~600k H/s sur CPU (Keccak optimisé)
- Difficulté par défaut 16 bits → ~1-10 sec sur CPU moderne
📈 Ajustement de Difficulté
Paramètres
| Paramètre | Valeur | Description |
|---|---|---|
TARGET_BLOCK_SECONDS |
15 | Temps cible par bloc |
DIFFICULTY_ADJUST_WINDOW |
24 | Fenêtre d'ajustement (blocs) |
DEFAULT_DIFFICULTY_BITS |
16 | Difficulté initiale |
Algorithme (nextDifficulty)
uint32_t nextDifficulty(blocks, now):
if blocks.size() <= WINDOW: return current
actual = now - timestamp[blocks.size() - W - 1]
expected = W * 15
if actual > expected * 3: return max(16, cur - 1) // trop lent → plus facile
if actual < expected / 3: return min(32, cur + 1) // trop rapide → plus dur
return cur
- Bornes : [16, 32] bits
- Fréquence : Tous les 24 blocs (~6 min à 15s/bloc)
💰 Récompense & Halving
Récompense de base
BASE_REWARD = "50000000000000000000" // 50 NST = 5×10¹⁹ nwei
HALVING_INTERVAL = 210000 // blocs (~4 ans à 15s/bloc)
Calcul (rewardAt)
u256 rewardAt(height):
r = BASE_REWARD
halvings = height / 210000
repeat halvings times: r >>= 1 // division exacte par 2
return r
- Bloc 0–209999 : 50 NST
- Bloc 210000–419999 : 25 NST
- Bloc 420000–629999 : 12.5 NST
- etc.
Frais de gas
-
fees = Σ (gasUsed_i * gasPrice_i)pour toutes txs du bloc - Total mineur =
reward + fees - Crédité via
state.addBalance(miner, total)
📥 Mempool & Exécution Transactions
Validation (addTx / runTx)
-
Chain ID :
tx.chainId == CHAIN_ID -
Signature :
tx.verified()+sender()valide -
Nonce :
tx.nonce == state.nonce(sender) -
Solde :
balance(sender) >= gasLimit*gasPrice + value -
Gas intrinsèque :
21000 + 100*data.size + (isCreate?32000:0) -
Exécution sur copie d'état (
State probe = state)- Transfert simple : déduction solde + crédit destinataire
- Création contrat :
deriveContractAddress,setCode,setStorage - Appel contrat :
vmExecuteavec gas restant
-
Remboursement :
unused = gasLimit - consumed→refund = unused * gasPrice - Échec → rollback (copie jetée), tx rejetée
Application Bloc (mineBlocks)
- Prend jusqu'à
MAX_TX_PER_BLOCK(256) txs valides du mempool - Exécute séquentiellement sur
State target = state - Accumule frais, met à jour
target - Calcule
stateRoot = target.stateRoot() txRoot = cumulativeRoot(txHashes)- Mine nonce (PoW)
- Commit :
state = target, retire txs minées du mempool
🔗 Validation Bloc (validateBlock)
bool validateBlock(b):
if b.height != blocks.size(): return false
if b.prevHash != (blocks.empty()? 0 : blocks.back().hash()): return false
if !b.validPoW(): return false
return true
📂 Fichiers
| Fichier | Contenu |
|---|---|
chain.hpp |
BlockHeader, Block, Chain API |
chain.cpp |
PoW, difficulté, récompense, mempool, exécution txs |
🔗 Voir aussi
- State-and-Accounts — Racine d'état, comptes
- Transactions — Sérialisation, gas, exécution
- Virtual-Machine-NVM — Appels contrat via VM
Cryptography — SHA-256, Keccak-256, secp256k1, ECDSA
Module :
src/sha256.hpp/.cpp,src/keccak.hpp/.cpp,src/crypto.hpp/.cpp
📘 About
Description : Implémentation from scratch de primitives cryptographiques essentielles :
- SHA-256 (FIPS 180-4) — hashage de blocs, vecteurs de test XKCP validés
- Keccak-256 — permutation Keccak-f[1600], standard Ethereum
- secp256k1 — courbe elliptique, ECDSA, ecrecover, RFC 6979 nonces déterministes
- HMAC-SHA256 — utilisation RFC 6979 pour génération nonce ECDSA
Tags : #cryptography #sha256 #keccak #secp256k1 #ecdsa #rfc6979 #hash #crypto
Auteur : Martial Zinsou
🔐 SHA-256 (FIPS 180-4)
Implémentation
-
Fichiers :
src/sha256.hpp/.cpp -
Classe :
SHA256(incrémental :update/digest) +sha256(bytes)stateless - Constantes : 8 mots d'init (racines carrées premiers 8 nombres premiers), 64 constantes de tour K64
- Padding : 1 bit '1', zéros, longueur 64 bits big-endian
- Bloc : 512 bits (64 octets), 64 tours
- Sortie : 256 bits (32 octets, big-endian)
Vecteurs de test (FIPS 180-4)
SHA256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
SHA256("abc") = ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
SHA256("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")
= 248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1
🔑 Keccak-256 / SHA3-256 (FIPS 202)
Implémentation
-
Fichiers :
src/keccak.hpp/.cpp -
Classes :
Keccak256(dom=0x01 défaut, dom=0x06 pour SHA3-256),keccak_f1600(uint64_t[25])cœur exposé -
Architecture éponge :
- État : 1600 bits = 25×64 bits (5×5 lanes)
- Taux (rate) : 1088 bits = 136 octets (Keccak-256)
- Capacité : 512 bits (sécurité 256 bits)
-
Permutation Keccak-f[1600] (24 tours) :
- θ (theta) : parité colonnes → XOR lignes
- ρ (rho) : rotation bit à bit par constantes RHO[5][5]
- π (pi) : permutation lanes (x,y) → (y, 2x+3y)
-
χ (chi) : non-linéaire par ligne
a = a ⊕ (¬b ∧ c) - ι (iota) : XOR constante de tour RC[round] sur lane (0,0)
-
Padding multi-rate :
dom | 0x00* | 0x80(dom=0x01 Keccak, 0x06 SHA3) - Sortie : 32 premiers octets de l'état (little-endian lanes → big-endian bytes)
Vecteur de test XKCP (permutation)
keccak_f1600(0) = f1258f7940e1dde7... (premiers 8 octets)
Vecteurs Keccak-256 (Ethereum)
Keccak256("") = c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
Keccak256("abc")= 4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45
📐 secp256k1 (Elliptic Curve)
Paramètres (SEC 2 / Standards for Efficient Cryptography)
p = FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F
= 2^256 - 2^32 - 977
n = FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141
(ordre du sous-groupe, premier)
Gx = 79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798
Gy = 483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8
Équation : y² = x³ + 7 (mod p) (courbe Koblitz, a=0, b=7)
Représentation des points
-
API publique : Coordonnées affines
(x,y)(PublicKeystruct) -
Interne : Coordonnées de Jacobien
(X,Y,Z)pour éviter inversions- Affine → Jacobien :
(x,y) → (x,y,1) - Jacobien → Affine :
x = X/Z², y = Y/Z³(une seule inversion à la fin)
- Affine → Jacobien :
Formules (Jacobien)
- Doublage (2P) : formule "dbl-2007-bl" (8M + 3S + 7A)
- Addition (P+Q, P≠Q) : formule "add-2007-bl" (11M + 5S + 9A)
-
Addition mixte (Jacobien + Affine) : optimisée pour
ecMulG
Multiplication scalaire
- Algorithme : Échelle binaire gauche→droite (MSB→LSB)
- Fenêtre : Non fenêtrée (simple, constant-time pour bits du scalaire)
- Précalcul : Aucun (MVP) — optimisable avec table 2⁴ points
✍️ ECDSA (RFC 6979 Déterministe)
Signature
Données : message hash z (32 octets), clé privée d (0 < d < n)
-
Génération k (RFC 6979) :
- HMAC-SHA256 DRBG avec clé =
d, message =z - Boucle jusqu'à
0 < k < n -
kdéterministe pour même(d,z)— pas d'entropie runtime
- HMAC-SHA256 DRBG avec clé =
-
Calcul signature :
-
R = k * G;r = R.x mod n(si r=0 → retry) s = k⁻¹(z + r*d) mod n-
Low-s : si
s > n/2→s = n - s(BIP-62, malléabilité) -
recid = (R.y & 1) | ((R.x >= n) ? 2 : 0)(parité y + débordement x)
-
Vérification
Données : z, (r,s), clé publique Q
w = s⁻¹ mod n-
u1 = z*w mod n,u2 = r*w mod n -
R = u1*G + u2*Q(addition Jacobien) - Valide si
R.x mod n == r
Récupération (ecrecover)
Données : z, (r,s,recid)
-
x = r + (recid & 2 ? n : 0)(gérer dépassement x ≥ n) -
y² = x³ + 7 mod p→y = sqrt(y²)viay = y²^((p+1)/4) mod p- Choisir
ytel quey & 1 == recid & 1(parité)
- Choisir
-
R = (x,y);e = z(entier) -
Q = r⁻¹(s*R - e*G)→ clé publique récupérée
🏠 Adresses Nstrike (Style Ethereum)
pubkey = 0x04 || x(32) || y(32) (65 octets, non-compressed)
address = keccak256(pubkey)[12..31] (20 derniers octets)
📂 Fichiers Sources
| Fichier | Description |
|---|---|
sha256.hpp/.cpp |
SHA-256 (classe + stateless) |
keccak.hpp/.cpp |
Keccak-256, SHA3-256, Keccak-f[1600] |
crypto.hpp/.cpp |
secp256k1, ECDSA, RFC6979, ecrecover, clés, adresses, HMAC-SHA256 |
🔗 Références
- FIPS 180-4 — SHA-256
- FIPS 202 — SHA-3/Keccak
- SEC 2 — Paramètres secp256k1
- RFC 6979 — ECDSA déterministe
- XKCP — Vecteurs Keccak officiels
- Ethereum Yellow Paper — Modèle de référence # ERC-20 Template — Contrat Jeton Compilé en NVM
Module :
src/vm.hpp/.cpp—erc20Bytecode(),erc20Deploy()
📘 About
Description : Fournir un jeton ERC-20 complet prêt à déployer, écrit en NVM assembly (compilé par assembleNvm), sans Solidity ni compilateur externe. Layout storage avec 6 slots clés (totalSupply, name, symbol, decimals, balances, allowance). Dispatch par selector sur les 4 premiers octets du calldata. 9 fonctions implémentées : totalSupply, name, symbol, decimals, balanceOf, transfer, approve, transferFrom, allowance. Fonction erc20Deploy pour déploiement avec init storage et mintage creator.
Tags : #erc20 #token #smart-contract #nvm #assembly #bytecode #deploy #mintage #solidity-alternative
Auteur : Martial Zinsou
🎯 Objectif
Fournir un jeton ERC-20 complet prêt à déployer, écrit en NVM assembly (compilé par assembleNvm), sans Solidity ni compilateur externe.
📦 Layout Storage (Slots)
| Slot | Clé | Contenu | Type |
|---|---|---|---|
| 0 | word(0) |
totalSupply |
u256 |
| 1 | word(1) |
name (ASCII left-aligned, 32 octets) |
fix32 |
| 2 | word(2) |
symbol (ASCII left-aligned) |
fix32 |
| 3 | word(3) |
decimals (u8 dans LSB) |
u256 |
| 4 | `keccak(addr32 \ | word(4))` | balances[addr] |
| 5 (inner) | `keccak(owner32 \ | word(5))` | allowance_inner |
| 5 (outer) | `keccak(spender32 \ | inner)` | allowance[owner][spender] |
Note :
word(s) = u256::toBytes(u256(s))(32 octets big-endian).
addr32 = u256::toBytes(u256::fromBytes(addr))(adresse right-aligned dans 32 octets).
🔀 Dispatch par Selector
Le premier mot du calldata (offset 0) = selector aligné à gauche (keccak(sig)[0..3] << 224).
CALLDATASIZE PUSH1 4 LT JUMPI L_revert
PUSH32 0 CALLDATALOAD
PUSH_SEL "totalSupply()" EQ JUMPI L_total
PUSH_SEL "name()" EQ JUMPI L_name
PUSH_SEL "symbol()" EQ JUMPI L_symbol
PUSH_SEL "decimals()" EQ JUMPI L_decimals
PUSH_SEL "balanceOf(address)" EQ JUMPI L_balance
PUSH_SEL "transfer(address,uint256)" EQ JUMPI L_transfer
PUSH_SEL "approve(address,uint256)" EQ JUMPI L_approve
PUSH_SEL "transferFrom(address,address,uint256)" EQ JUMPI L_from
PUSH_SEL "allowance(address,address)" EQ JUMPI L_allow
REVERT
📋 Fonctions Implémentées (9)
| Fonction | Selector | Args (offset) | Retour | Description |
|---|---|---|---|---|
totalSupply() |
0x18160ddd | — | supply |
Offre totale |
name() |
0x06fdde03 | — |
name (32 octets) |
Nom du jeton |
symbol() |
0x95d89b41 | — | symbol |
Symbole |
decimals() |
0x313ce567 | — |
decimals (u8) |
Décimales |
balanceOf(address) |
0x70a08231 |
addr@4 |
balance |
Solde d'une adresse |
transfer(addr,uint) |
0xa9059cbb |
to@4, amt@36 |
1/0
|
Transfert |
approve(addr,uint) |
0x095ea7b3 |
spender@4, amt@36 |
1/0
|
Autorisation |
transferFrom(f,t,amt) |
0x23b872dd |
from@4, to@36, amt@68 |
1/0
|
Transfert délégué |
allowance(o,s) |
0xdd62ed3e |
owner@4, spender@36 |
allowance |
Autorisation restante |
🔧 Implémentation Clé (Extraits)
balanceOf
L_balance:
PUSH32 4 CALLDATALOAD // addr
PUSH1 4 KECCAK SLOAD // key=keccak(addr||4), load
PUSH1 1 RESULT STOP
transfer
L_transfer:
PUSH32 4 CALLDATALOAD // to
DUP1 ISZERO JUMPI L_revert // reject to==0
CALLER PUSH1 4 KECCAK DUP1 SLOAD // keyC, balC
PUSH32 36 CALLDATALOAD // amount
DUP2 LT ISZERO JUMPI L_revert // balC < amount → revert
SWAP1 SUB SSTORE // balC -= amount
PUSH32 4 CALLDATALOAD PUSH1 4 KECCAK DUP1 SLOAD
PUSH32 36 CALLDATALOAD ADD SSTORE // bal(to) += amount
PUSH1 1 RESULT STOP
approve
L_approve:
CALLER PUSH1 5 KECCAK // inner = keccak(caller||5)
PUSH32 4 CALLDATALOAD SWAP1 KECCAK // key = keccak(spender||inner)
PUSH32 36 CALLDATALOAD SSTORE // allowance = amount
PUSH1 1 RESULT STOP
transferFrom
L_from:
PUSH32 4 CALLDATALOAD PUSH1 5 KECCAK // innerF
CALLER SWAP1 KECCAK DUP1 SLOAD // keyA, allowed
PUSH32 68 CALLDATALOAD // amount (arg2)
DUP2 LT ISZERO JUMPI L_revert // allowed < amount → revert
SWAP1 SUB SSTORE // allowed -= amount
// debit from
PUSH32 4 CALLDATALOAD PUSH1 4 KECCAK DUP1 SLOAD
PUSH32 68 CALLDATALOAD
DUP2 LT ISZERO JUMPI L_revert
SWAP1 SUB SSTORE
// credit to
PUSH32 36 CALLDATALOAD PUSH1 4 KECCAK DUP1 SLOAD
PUSH32 68 CALLDATALOAD ADD SSTORE
PUSH1 1 RESULT STOP
🚀 Déploiement (erc20Deploy)
fix20 erc20Deploy(State& state, const fix20& creator, const u256& creatorNonce,
const std::string& name, const std::string& symbol,
uint8_t decimals, const u256& initialSupply);
addr = deriveContractAddress(creator, creatorNonce)state.setCode(addr, erc20Bytecode())- Storage init :
slot 0 = initialSupply-
slot 1 = name(ASCII left-aligned 32 octets) slot 2 = symbol-
slot 3 = decimals(LSB) -
balances[creator] = initialSupplyviakeccak(creator||word(4))
creatorNonce++
📂 Fichiers
| Fichier | Contenu |
|---|---|
vm.hpp |
API erc20Bytecode(), erc20Deploy()
|
vm.cpp |
Source assembleur ERC20_SRC + implémentation déploiement |
🔗 Voir aussi
- EIP-20 — Standard ERC-20
- ABI Specification — Selector encoding
- Virtual-Machine-NVM — VM & assembleur
RPC Specification — JSON-RPC 2.0
Module :
src/rpc.hpp/.cpp
📘 About
Description : Serveur JSON-RPC 2.0 en stdio avec 9 endpoints supportés : getbalance, sendtx, getblock, getblockcount, getgasprice, mine, chainstatus, deploytoken, callcontract. Format requête/réponse standard JSON-RPC 2.0. Formats de données : address (20 octets hex), quantity/u256 (64 hex chars), boolean, string. Codes d'erreur : -32700 (parse), -32600 (invalid request), -32601 (method not found), -32602 (invalid params), -32603 (internal). Mode interaction : lecture stdin, écriture stdout.
Tags : #json-rpc #rpc #stdio #api #endpoints #json #method #getbalance #sendtx #getblock
Auteur : Martial Zinsou
🌐 Endpoints JSON-RPC Supportés
Requête JSON-RPC 2.0 Standard
{
"jsonrpc": "2.0",
"method": "<method_name>",
"params": [...],
"id": <request_id>
}
Réponse JSON-RPC 2.0 Succès
{
"jsonrpc": "2.0",
"result": <result>,
"id": <request_id>
}
Réponse JSON-RPC 2.0 Erreur
{
"jsonrpc": "2.0",
"error": {
"code": <error_code>,
"message": "<error_message>"
},
"id": <request_id>
}
📋 Méthodes Supportées
1. getbalance
Description : Récupérer le solde d'une adresse.
Paramètres
[{"address": "0xabc123..."}]
Retour
"0x0000000000000000000000000000000000000000000000000000000000000000" (hex string, nwei)
Exemple
curl -X POST http://localhost:8080 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"getbalance","params":[{"address":"0xabc123"}],"id":1}'
2. sendtx
Description : Envoyer une transaction signée.
Paramètres
[{"tx": "0x..."}] // Transaction hexadécimale sérialisée
Retour
"0x<hash_32_bytes_hex>" (hash de la transaction)
Exemple
curl -X POST http://localhost:8080 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"sendtx","params":[{"tx":"0x0123456789abcdef..."}],"id":1}'
3. getblock
Description : Récupérer les informations d'un bloc par hauteur.
Paramètres
[{"height": 5}]
Retour
{
"hash": "0x...",
"height": 5,
"timestamp": 1234567890,
"miner": "0x...",
"txCount": 3,
"transactions": ["0x...", "0x...", "0x..."]
}
4. getblockcount
Description : Récupérer la hauteur actuelle du bloc (dernier bloc).
Paramètres : [] (vide)
Retour
"0x5" (hex string, hauteur actuelle)
5. getgasprice
Description : Récupérer le prix de gas suggéré.
Paramètres : [] (vide)
Retour
"0x1" (hex string, 1 nwei = prix minimum)
Remarque : Prix fixe de 1 nwei par unité de gas (configuration par défaut).
6. mine
Description : Miner des blocs pour une adresse donnée.
Paramètres
[{"miner": "0xabc123...", "count": 1}]
Retour
{
"status": "success",
"blocksMined": 1,
"newHeight": 1,
"reward": "0x32000000000000000000" // 50 NST en hex
}
7. chainstatus
Description : Récupérer l'état complet de la chaîne.
Paramètres : [] (vide)
Retour
{
"status": "synced",
"currentHeight": 15,
"difficultyBits": 16,
"blockTime": 15,
"chainId": 1
}
8. deploytoken
Description : Déployer un contrat jeton ERC-20.
Paramètres
[{"creator": "0xabc123...", "name": "MyToken", "symbol": "MTK", "decimals": 18, "initialSupply": "0x1000000000000000000"}]
Retour
"0xabc123..." (adresse du contrat déployé)
9. callcontract
Description : Appeler une fonction de contrat.
Paramètres
[{"contract": "0xabc123...", "data": "0x..." , "gas": "0x5208", "from": "0xdef456..."}]
Retour
"0x..." (résultat de l'appel VM, hex string)
🔌 Serveur RPC en Ligne de Commande (rpcServeStdio)
Mode d'Interaction
Le serveur RPC lit les requêtes JSON depuis stdin et renvoie les réponses sur stdout.
# Démarrage
./build/nstrike rpc
# Envoi d'une requête (via un autre terminal ou pipe)
echo '{"jsonrpc":"2.0","method":"getbalance","params":[{"address":"0xabc123"}],"id":1}' | ./build/nstrike rpc
# Sortie attendue
{"jsonrpc":"2.0","result":"0x0000000000000000000000000000000000000000000000000000000000000000","id":1}
Implémentation C++
void rpcServeStdio(Chain& chain) {
std::string line;
while (std::getline(std::cin, line)) {
if (line.empty()) continue;
Json req;
if (!Json::tryParse(line, req)) {
Json resp = errResp(-32700, "parse error", req.value("id", Json::Null));
fputs(resp.dump().c_str(), stdout);
fputc('\n', stdout);
fflush(stdout);
continue;
}
Json resp = rpcHandle(req, chain);
fputs(resp.dump().c_str(), stdout);
fputc('\n', stdout);
fflush(stdout);
}
}
Gestion des Erreurs
| Code | Message | Description |
|---|---|---|
-32700 |
Parse error | Requête JSON invalide |
-32600 |
Invalid Request | Méthode inconnue ou version JSON-RPC non supportée |
-32601 |
Method not found | La méthode n'existe pas |
-32602 |
Invalid params | Paramètres invalides |
-32603 |
Internal error | Erreur interne au traitement |
📦 Formats de Données
Adresse (address)
- Format : 20 octets right-aligned dans 32 octets
- Hexadécimal :
0x+ 40 caractères hexadécimaux - Exemple :
0xabc123def4567890123456789012345678901234
Nonce (quantity / u256)
- Format : hexadécimal non signé 256 bits
- Hexadécimal :
0x+ 64 caractères hexadécimaux - Exemple :
0x0000000000000000000000000000000000000000000000000000000000000000
Booléen (boolean)
-
true/false(JSON standard)
Chaîne (string)
- JSON standard, UTF-8 supporté
- Exemple :
"0xabc123..."
📂 Fichiers
| Fichier | Contenu |
|---|---|
rpc.hpp |
Définitions d'endpoints, structures de requête/réponse |
rpc.cpp |
Implémentation serveur JSON-RPC, gestion des méthodes |
🔗 Voir aussi
- CLI-Reference — Commandes en ligne de commande
- Virtual-Machine-NVM — Appels de contrat via RPC
- Chain-and-Consensus — État de la chaîne, minage
State and Accounts — Comptes, Stockage, Racines d'État
Module :
src/state.hpp/.cpp
📘 About
Description : Modèle de compte complet avec adresse (fix20), nonce (u256), balance (u256), codeHash (fix32), storageRoot (fix32). Slots storage : slot 0 = totalSupply, slot 1 = name, slot 2 = symbol, slot 3 = decimals, slot 4 = balances[addr] via keccak, slot 5 = allowance via double-keccak. Racines d'état : stateRoot(), storageRoot(), codeHash() implémentées avec keccak accumulative hashing. Validation nonce, protection replay CHAIN_ID.
Tags : #state #accounts #storage #root #hash #account-model #u256 #fix32 #keccak
Auteur : Martial Zinsou
👤 Modèle de Compte
Structure Account
| Champ | Type | Description |
|---|---|---|
address |
fix20 |
Adresse (20 octets, right-aligned dans 32 octets) |
nonce |
u256 |
Nonce du compte (incrémente à chaque tx) |
balance |
u256 |
Solde en nwei (1 NST = 10⁹ nwei) |
codeHash |
fix32 |
Hash du code bytecode (keccak256) |
storageRoot |
fix32 |
Racine d'arbre de stockage (patricia) |
Déduction d'Adresse
fix20 deriveAddress(const fix20& privKey) {
// Keccak(privKey || 0x00) → last 20 bytes
bytes k = Keccak256::hash(bytes(privKey.data(), privKey.size()) + bytes("\x00"));
return fix20(k.end() - 20, k.end());
}
Nonce & Replay Protection
-
state.nonce(addr)→ u256 -
tx.noncedoit égalerstate.nonce(sender)✅ -
++state.nonce(sender)après exécution réussie -
CHAIN_IDinclus dans la signature (protection replay cross-chain)
💾 Stockage Contract (Storage Model)
Slots de Storage (32 octets = word)
| Slot | Clé (keccak) | Contenu | Type |
|---|---|---|---|
| 0 | — |
totalSupply (template ERC-20) |
u256 |
| 1 | — |
name (template ERC-20, ASCII left-aligned) |
fix32 |
| 2 | — |
symbol (template ERC-20) |
fix32 |
| 3 | — |
decimals (template ERC-20, u8 LSB) |
u256 |
| 4 | `keccak(addr32 \ | word(4))` | balances[addr] |
| 5 | `keccak(spender32 \ | keccak(owner32 \ | word(5)))` |
Accès Stockage
u256 State::getBalance(const fix20& addr) const {
fix32 key = Keccak256::hash(
bytes(u256::toBytes(u256(4))).append(
bytes(addr.data(), addr.size())
));
return SLOAD(key);
}
void State::setBalance(const fix20& addr, u256 bal) {
fix32 key = Keccak256::hash(
bytes(u256::toBytes(u256(4))).append(
bytes(addr.data(), addr.size())
));
SSTORE(key, bal);
}
u256 State::getAllowance(const fix20& owner, const fix20& spender) const {
fix32 innerKey = Keccak256::hash(
bytes(spender.data(), spender.size()).append(
Keccak256::hash(bytes(owner.data(), owner.size()))
);
fix32 key = Keccak256::hash(bytes(u256::toBytes(u256(5))).append(bytes(innerKey)));
return SLOAD(key);
}
void State::setAllowance(const fix20& owner, const fix20& spender, u256 amt) {
fix32 innerKey = Keccak256::hash(
bytes(spender.data(), spender.size()).append(
Keccak256::hash(bytes(owner.data(), owner.size()))
);
fix32 key = Keccak256::hash(bytes(u256::toBytes(u256(5))).append(bytes(innerKey)));
SSTORE(key, amt);
}
Stockage Universel (non-ERC-20)
Pour les comptes de base (code, storage patricia) :
| Slot | Clé | Contenu |
|---|---|---|
| `keccak(addr32 \ | word(0))` | nonce |
| `keccak(addr32 \ | word(1))` | balance |
| `keccak(addr32 \ | word(2))` | codeHash |
| `keccak(addr32 \ | word(3))` | storageRoot |
🌳 Racines d'État (State Roots)
stateRoot() Global
fix32 State::stateRoot() const {
fix32 h = fix32(HASH_ZERO);
for (auto& kv : accounts) {
fix32 addrW = u256::toBytes(u256::fromBytes(k.first.data(), k.first.size()));
fix32 nonceW = u256::toBytes(k.second.nonce);
fix32 balW = u256::toBytes(k.second.balance);
fix32 codeW = k.second.code.empty() ? HASH_ZERO : keccak256(k.second.code);
fix32 storW = storageRoot(k.first); // racine de storage du compte
fix32 leaf = keccak(addrW.append(nonceW).append(balW).append(codeW).append(storW));
h = keccak(h.append(leaf));
}
return h;
}
storageRoot(const fix20& account)
fix32 State::storageRoot(const fix20& a) const {
fix32 h = fix32(HASH_ZERO);
for (auto& sv : storage[a]) {
fix32 keyW = u256::toBytes(u256::fromBytes(sv.first.data(), sv.first.size()));
fix32 valW = u256::toBytes(sv.second);
fix32 leaf = keccac(keyW.append(valW));
h = keccak(h.append(leaf));
}
return h;
}
codeHash(const fix20& account)
fix32 State::codeHash(const fix20& a) const {
auto code = getCode(a);
return code.empty() ? HASH_ZERO : keccak256(code);
}
📂 Fichiers
| Fichier | Contenu |
|---|---|
state.hpp |
Account, State API, stockage, racines |
state.cpp |
Implémentation SLOAD/SSTORE, racines, adresses |
🔗 Voir aussi
- Transactions — Utilisation du storage dans txs
- Virtual-Machine-NVM — SLOAD/SSTORE en VM
- Chain-and-Consensus — Blocs, état global
Top comments (0)