DEV Community

Martial Zinsou
Martial Zinsou

Posted on

Nstrike — Blockchain légère (MVP)

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
Enter fullscreen mode Exit fullscreen mode

⚙️ 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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

💻 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

⚠️ 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
Enter fullscreen mode Exit fullscreen mode

Transfert NST

# <privHex> <dest> <montant en nwei> [gasPrice en nwei]
./build/nstrike send 0x2e066... 0xabc123... 0x0de0b6b3a7640000 0x1
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

État de la Blockchain

./build/nstrike chain
Enter fullscreen mode Exit fullscreen mode

Serveur JSON-RPC (stdio)

# Lance le serveur RPC sur l'entrée/sortie standard (Ctrl-D pour quitter)
./build/nstrike rpc
Enter fullscreen mode Exit fullscreen mode

Exemple de requête RPC :

{"jsonrpc":"2.0","method":"getbalance","params":{"address":"0x3ab19..."},"id":1}
Enter fullscreen mode Exit fullscreen mode

Réponse :

{"jsonrpc":"2.0","result":"0x2b5e3af16b1880000","id":1}
Enter fullscreen mode Exit fullscreen mode

📡 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
Enter fullscreen mode Exit fullscreen mode

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 : KECCAK pop a,b → push keccak(a||b) — utilisé pour clés de mapping
  • Sauts : JUMP(dest), JUMPI(dest,cond)dest doit être JUMPDEST
  • Résultat : RESULT(n) → sortie n mots
  • 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))
  • 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 avoir difficulty zé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 (runTx sur 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 test obligatoire

🤝 Contribution

Les contributions sont les bienvenues ! Merci de :

  1. Forker le dépôt
  2. Créer une branche feature/ma-fonctionnalite
  3. Ajouter des tests dans tests/test_main.cpp
  4. make test doit passer
  5. 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);
};
Enter fullscreen mode Exit fullscreen mode

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]*
Enter fullscreen mode Exit fullscreen mode

⛏️ Proof-of-Work (PoW Léger)

Algorithme

  1. h1 = Keccak256(header_serialisé)
  2. h2 = Keccak256(h1) (double hachage style Bitcoin)
  3. Valide si h2 a au moins difficultyBits zé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;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • 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)
Enter fullscreen mode Exit fullscreen mode

Calcul (rewardAt)

u256 rewardAt(height):
    r = BASE_REWARD
    halvings = height / 210000
    repeat halvings times: r >>= 1  // division exacte par 2
    return r
Enter fullscreen mode Exit fullscreen mode
  • 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)

  1. Chain ID : tx.chainId == CHAIN_ID
  2. Signature : tx.verified() + sender() valide
  3. Nonce : tx.nonce == state.nonce(sender)
  4. Solde : balance(sender) >= gasLimit*gasPrice + value
  5. Gas intrinsèque : 21000 + 100*data.size + (isCreate?32000:0)
  6. 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 : vmExecute avec gas restant
  7. Remboursement : unused = gasLimit - consumedrefund = unused * gasPrice
  8. Échec → rollback (copie jetée), tx rejetée

Application Bloc (mineBlocks)

  1. Prend jusqu'à MAX_TX_PER_BLOCK (256) txs valides du mempool
  2. Exécute séquentiellement sur State target = state
  3. Accumule frais, met à jour target
  4. Calcule stateRoot = target.stateRoot()
  5. txRoot = cumulativeRoot(txHashes)
  6. Mine nonce (PoW)
  7. 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
Enter fullscreen mode Exit fullscreen mode

📂 Fichiers

Fichier Contenu
chain.hpp BlockHeader, Block, Chain API
chain.cpp PoW, difficulté, récompense, mempool, exécution txs

🔗 Voir aussi

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
Enter fullscreen mode Exit fullscreen mode

🔑 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) :
    1. θ (theta) : parité colonnes → XOR lignes
    2. ρ (rho) : rotation bit à bit par constantes RHO[5][5]
    3. π (pi) : permutation lanes (x,y) → (y, 2x+3y)
    4. χ (chi) : non-linéaire par ligne a = a ⊕ (¬b ∧ c)
    5. ι (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)
Enter fullscreen mode Exit fullscreen mode

Vecteurs Keccak-256 (Ethereum)

Keccak256("")   = c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
Keccak256("abc")= 4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45
Enter fullscreen mode Exit fullscreen mode

📐 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
Enter fullscreen mode Exit fullscreen mode

Équation : y² = x³ + 7 (mod p) (courbe Koblitz, a=0, b=7)

Représentation des points

  • API publique : Coordonnées affines (x,y) (PublicKey struct)
  • 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)

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)

  1. Génération k (RFC 6979) :

    • HMAC-SHA256 DRBG avec clé = d, message = z
    • Boucle jusqu'à 0 < k < n
    • k déterministe pour même (d,z) — pas d'entropie runtime
  2. 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/2s = 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

  1. w = s⁻¹ mod n
  2. u1 = z*w mod n, u2 = r*w mod n
  3. R = u1*G + u2*Q (addition Jacobien)
  4. Valide si R.x mod n == r

Récupération (ecrecover)

Données : z, (r,s,recid)

  1. x = r + (recid & 2 ? n : 0) (gérer dépassement x ≥ n)
  2. y² = x³ + 7 mod py = sqrt(y²) via y = y²^((p+1)/4) mod p
    • Choisir y tel que y & 1 == recid & 1 (parité)
  3. R = (x,y) ; e = z (entier)
  4. 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)
Enter fullscreen mode Exit fullscreen mode

📂 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

Module : src/vm.hpp/.cpperc20Bytecode(), 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
Enter fullscreen mode Exit fullscreen mode

📋 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

🚀 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);
Enter fullscreen mode Exit fullscreen mode
  1. addr = deriveContractAddress(creator, creatorNonce)
  2. state.setCode(addr, erc20Bytecode())
  3. Storage init :
    • slot 0 = initialSupply
    • slot 1 = name (ASCII left-aligned 32 octets)
    • slot 2 = symbol
    • slot 3 = decimals (LSB)
    • balances[creator] = initialSupply via keccak(creator||word(4))
  4. creatorNonce++

📂 Fichiers

Fichier Contenu
vm.hpp API erc20Bytecode(), erc20Deploy()
vm.cpp Source assembleur ERC20_SRC + implémentation déploiement

🔗 Voir aussi

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>
}
Enter fullscreen mode Exit fullscreen mode

Réponse JSON-RPC 2.0 Succès

{
  "jsonrpc": "2.0",
  "result": <result>,
  "id": <request_id>
}
Enter fullscreen mode Exit fullscreen mode

Réponse JSON-RPC 2.0 Erreur

{
  "jsonrpc": "2.0",
  "error": {
    "code": <error_code>,
    "message": "<error_message>"
  },
  "id": <request_id>
}
Enter fullscreen mode Exit fullscreen mode

📋 Méthodes Supportées

1. getbalance

Description : Récupérer le solde d'une adresse.

Paramètres

[{"address": "0xabc123..."}]
Enter fullscreen mode Exit fullscreen mode

Retour

"0x0000000000000000000000000000000000000000000000000000000000000000" (hex string, nwei)
Enter fullscreen mode Exit fullscreen mode

Exemple

curl -X POST http://localhost:8080 \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"getbalance","params":[{"address":"0xabc123"}],"id":1}'
Enter fullscreen mode Exit fullscreen mode

2. sendtx

Description : Envoyer une transaction signée.

Paramètres

[{"tx": "0x..."}]  // Transaction hexadécimale sérialisée
Enter fullscreen mode Exit fullscreen mode

Retour

"0x<hash_32_bytes_hex>" (hash de la transaction)
Enter fullscreen mode Exit fullscreen mode

Exemple

curl -X POST http://localhost:8080 \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"sendtx","params":[{"tx":"0x0123456789abcdef..."}],"id":1}'
Enter fullscreen mode Exit fullscreen mode

3. getblock

Description : Récupérer les informations d'un bloc par hauteur.

Paramètres

[{"height": 5}]
Enter fullscreen mode Exit fullscreen mode

Retour

{
  "hash": "0x...",
  "height": 5,
  "timestamp": 1234567890,
  "miner": "0x...",
  "txCount": 3,
  "transactions": ["0x...", "0x...", "0x..."]
}
Enter fullscreen mode Exit fullscreen mode

4. getblockcount

Description : Récupérer la hauteur actuelle du bloc (dernier bloc).

Paramètres : [] (vide)

Retour

"0x5" (hex string, hauteur actuelle)
Enter fullscreen mode Exit fullscreen mode

5. getgasprice

Description : Récupérer le prix de gas suggéré.

Paramètres : [] (vide)

Retour

"0x1" (hex string, 1 nwei = prix minimum)
Enter fullscreen mode Exit fullscreen mode

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}]
Enter fullscreen mode Exit fullscreen mode

Retour

{
  "status": "success",
  "blocksMined": 1,
  "newHeight": 1,
  "reward": "0x32000000000000000000"  // 50 NST en hex
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

8. deploytoken

Description : Déployer un contrat jeton ERC-20.

Paramètres

[{"creator": "0xabc123...", "name": "MyToken", "symbol": "MTK", "decimals": 18, "initialSupply": "0x1000000000000000000"}]
Enter fullscreen mode Exit fullscreen mode

Retour

"0xabc123..." (adresse du contrat déployé)
Enter fullscreen mode Exit fullscreen mode

9. callcontract

Description : Appeler une fonction de contrat.

Paramètres

[{"contract": "0xabc123...", "data": "0x..." , "gas": "0x5208", "from": "0xdef456..."}]
Enter fullscreen mode Exit fullscreen mode

Retour

"0x..." (résultat de l'appel VM, hex string)
Enter fullscreen mode Exit fullscreen mode

🔌 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}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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

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());
}
Enter fullscreen mode Exit fullscreen mode

Nonce & Replay Protection

  • state.nonce(addr) → u256
  • tx.nonce doit égaler state.nonce(sender)
  • ++state.nonce(sender) après exécution réussie
  • CHAIN_ID inclus 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);
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

codeHash(const fix20& account)

fix32 State::codeHash(const fix20& a) const {
    auto code = getCode(a);
    return code.empty() ? HASH_ZERO : keccak256(code);
}
Enter fullscreen mode Exit fullscreen mode

📂 Fichiers

Fichier Contenu
state.hpp Account, State API, stockage, racines
state.cpp Implémentation SLOAD/SSTORE, racines, adresses

🔗 Voir aussi

Top comments (0)